[
  {
    "path": ".github/workflows/cmake.yml",
    "content": "name: CMake\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n\n\nenv:\n  # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)\n  BUILD_TYPE: Release\n\n\n\njobs:\n  build:\n    # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.\n    # You can convert this to a matrix build if you need cross-platform coverage.\n    # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix\n    runs-on: windows-latest\n    env:\n      # Set your boost version\n      BOOST_VERSION: 1.78.0\n      # Set you boost path to the default one (I don't know if you can use variables here)\n      BOOST_PATH: ${{github.workspace}}/boost/\n      \n    steps:\n    - uses: actions/checkout@v3\n  \n    - name: Checkout submodules\n      run: git submodule update --init --recursive\n    \n# Retrieve the cache, uses cache@v2\n    - name: Cache boost\n      uses: actions/cache@v2\n      id: cache-boost\n      with:\n        # Set the path to cache\n        path: ${{env.BOOST_PATH}}\n        # Use the version as the key to only cache the correct version\n        key: boost-${{env.BOOST_VERSION}}\n\n    # Actual install step (only runs if the cache is empty)\n    - name: Install boost\n      if: steps.cache-boost.outputs.cache-hit != 'true'\n      uses: MarkusJx/install-boost@v2.3.0\n      with:\n        # Set the boost version (required)\n        boost_version: ${{env.BOOST_VERSION}}\n        # Set the install directory\n        boost_install_dir: ${{env.BOOST_PATH}}\n        # Set your platform version\n        platform_version: 2022\n        # Set the toolset\n        toolset: msvc\n\n    - name: Configure CMake\n      # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.\n      # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type\n      run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}\n      env:\n        BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }}\n\n\n    - name: Build\n      # Build your program with the given configuration\n      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Ignore user's workspace\nworkspace/\n\n# Build directory\nbuild/\n\n# clangd cache and code completion files\n.cache\ncompile_commands.json\n\n# Blender temporary files\n*.blend1\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"dependencies/submodules/delta-studio\"]\n\tpath = dependencies/submodules/delta-studio\n\turl = https://github.com/ange-yaghi/delta-studio\n[submodule \"dependencies/submodules/simple-2d-constraint-solver\"]\n\tpath = dependencies/submodules/simple-2d-constraint-solver\n\turl = https://github.com/ange-yaghi/simple-2d-constraint-solver\n[submodule \"dependencies/submodules/direct-to-video\"]\n\tpath = dependencies/submodules/direct-to-video\n\turl = https://github.com/ange-yaghi/direct-to-video\n[submodule \"dependencies/submodules/csv-io\"]\n\tpath = dependencies/submodules/csv-io\n\turl = https://github.com/ange-yaghi/csv-io\n[submodule \"dependencies/submodules/piranha\"]\n\tpath = dependencies/submodules/piranha\n\turl = https://github.com/ange-yaghi/piranha.git\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.10)\n\noption(DTV \"Enable video output\" OFF)\noption(PIRANHA_ENABLED \"Enable scripting input\" ON)\noption(DISCORD_ENABLED \"Enable Discord Rich Presence\" ON)\n\nif (DTV)\n    add_compile_definitions(ATG_ENGINE_SIM_VIDEO_CAPTURE)\nendif (DTV)\n\nif (PIRANHA_ENABLED)\n    add_compile_definitions(ATG_ENGINE_SIM_PIRANHA_ENABLED)\nendif (PIRANHA_ENABLED)\n\nif (DISCORD_ENABLED)\n    add_compile_definitions(ATG_ENGINE_SIM_DISCORD_ENABLED)\nendif (DISCORD_ENABLED)\n\n# Enable group projects in folders\nset_property(GLOBAL PROPERTY USE_FOLDERS ON)\nset_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER \"cmake\")\n\nproject(engine-sim)\n\nset(CMAKE_CXX_STANDARD 17)\n\n# ========================================================\n# GTEST\n\ninclude(FetchContent)\nFetchContent_Declare(\n    googletest\n    URL\n    https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip\n)\n\nset(gtest_force_shared_crt ON CACHE BOOL \"\" FORCE)\nFetchContent_MakeAvailable(googletest)\n\nset_property(TARGET gmock PROPERTY FOLDER \"gtest\")\nset_property(TARGET gmock_main PROPERTY FOLDER \"gtest\")\nset_property(TARGET gtest PROPERTY FOLDER \"gtest\")\nset_property(TARGET gtest_main PROPERTY FOLDER \"gtest\")\n\n# ========================================================\n\nadd_library(engine-sim STATIC\n    # Source files\n    src/audio_buffer.cpp\n    src/camshaft.cpp\n    src/crankshaft.cpp\n    src/combustion_chamber.cpp\n    src/connecting_rod.cpp\n    src/convolution_filter.cpp\n    src/cylinder_bank.cpp\n    src/cylinder_head.cpp\n    src/delay_filter.cpp\n    src/derivative_filter.cpp\n    src/direct_throttle_linkage.cpp\n    src/dynamometer.cpp\n    src/engine.cpp\n    src/exhaust_system.cpp\n    src/feedback_comb_filter.cpp\n    src/filter.cpp\n    src/fuel.cpp\n    src/function.cpp\n    src/gas_system.cpp\n    src/gaussian_filter.cpp\n    src/governor.cpp\n    src/ignition_module.cpp\n    src/impulse_response.cpp\n    src/intake.cpp\n    src/jitter_filter.cpp\n    src/leveling_filter.cpp\n    src/low_pass_filter.cpp\n    src/part.cpp\n    src/piston.cpp\n    src/piston_engine_simulator.cpp\n    src/simulator.cpp\n    src/standard_valvetrain.cpp\n    src/starter_motor.cpp\n    src/synthesizer.cpp\n    src/throttle.cpp\n    src/transmission.cpp\n    src/utilities.cpp\n    src/valvetrain.cpp\n    src/vehicle.cpp\n    src/vehicle_drag_constraint.cpp\n    src/vtec_valvetrain.cpp\n\n    # Include files\n    include/audio_buffer.h\n    include/application_settings.h\n    include/camshaft.h\n    include/crankshaft.h\n    include/combustion_chamber.h\n    include/connecting_rod.h\n    include/convolution_filter.h\n    include/cylinder_bank.h\n    include/cylinder_head.h\n    include/delay_filter.h\n    include/derivative_filter.h\n    include/direct_throttle_linkage.h\n    include/dynamometer.h\n    include/engine.h\n    include/exhaust_system.h\n    include/feedback_comb_filter.h\n    include/filter.h\n    include/fuel.h\n    include/function.h\n    include/gas_system.h\n    include/gaussian_filter.h\n    include/governor.h\n    include/ignition_module.h\n    include/impulse_response.h\n    include/intake.h\n    include/jitter_filter.h\n    include/leveling_filter.h\n    include/low_pass_filter.h\n    include/part.h\n    include/piston.h\n    include/piston_engine_simulator.h\n    include/simulator.h\n    include/standard_valvetrain.h\n    include/starter_motor.h\n    include/synthesizer.h\n    include/throttle.h\n    include/transmission.h\n    include/units.h\n    include/utilities.h\n    include/valvetrain.h\n    include/vehicle.h\n    include/vehicle_drag_constraint.h\n    include/vtec_valvetrain.h\n)\n\ntarget_link_libraries(engine-sim\n    simple-2d-constraint-solver\n    csv-io\n    delta-basic)\n\ntarget_include_directories(engine-sim\n    PUBLIC dependencies/submodules)\n\nif (PIRANHA_ENABLED)\n    add_library(engine-sim-script-interpreter STATIC\n        # Source files\n        scripting/src/channel_types.cpp\n        scripting/src/compiler.cpp\n        scripting/src/engine_context.cpp\n        scripting/src/language_rules.cpp\n\n        # Include files\n        scripting/include/actions.h\n        scripting/include/camshaft_node.h\n        scripting/include/channel_types.h\n        scripting/include/compiler.h\n        scripting/include/connecting_rod_node.h\n        scripting/include/crankshaft_node.h\n        scripting/include/cylinder_bank_node.h\n        scripting/include/cylinder_head_node.h\n        scripting/include/engine_context.h\n        scripting/include/engine_node.h\n        scripting/include/engine_sim.h\n        scripting/include/exhaust_system_node.h\n        scripting/include/intake_node.h\n        scripting/include/function_node.h\n        scripting/include/ignition_module_node.h\n        scripting/include/ignition_wire_node.h\n        scripting/include/impulse_response_node.h\n        scripting/include/intake_node.h\n        scripting/include/language_rules.h\n        scripting/include/node.h\n        scripting/include/object_reference_node.h\n        scripting/include/object_reference_node_output.h\n        scripting/include/piranha.h\n        scripting/include/piston_node.h\n        scripting/include/rod_journal_node.h\n        scripting/include/standard_valvetrain_node.h\n        scripting/include/transmission_node.h\n        scripting/include/valvetrain_node.h\n        scripting/include/vtec_valvetrain_node.h\n\t\tscripting/include/vehicle_node.h\n    )\n\n    target_include_directories(engine-sim-script-interpreter\n        PUBLIC dependencies/submodules)\n\n    target_link_libraries(engine-sim-script-interpreter\n        csv-io\n        piranha)\nendif (PIRANHA_ENABLED)\n\nif (DISCORD_ENABLED)\n    add_library(discord STATIC\n        # Source files\n        dependencies/discord/Discord.cpp\n\n        # Include files\n        dependencies/discord/Discord.h\n        dependencies/discord/discord_register.h\n        dependencies/discord/discord_rpc.h\n    )\nendif (DISCORD_ENABLED)\n\nadd_executable(engine-sim-app WIN32\n    # Source files\n    src/main.cpp\n    src/engine_sim_application.cpp\n    src/geometry_generator.cpp\n    src/simulation_object.cpp\n    src/piston_object.cpp\n    src/connecting_rod_object.cpp\n    src/ui_element.cpp\n    src/ui_manager.cpp\n    src/cylinder_pressure_gauge.cpp\n    src/ui_math.cpp\n    src/gauge.cpp\n    src/crankshaft_object.cpp\n    src/cylinder_bank_object.cpp\n    src/cylinder_head_object.cpp\n    src/ui_button.cpp\n    src/ui_utilities.cpp\n    src/combustion_chamber_object.cpp\n    src/oscilloscope.cpp\n    src/shaders.cpp\n    src/engine_view.cpp\n    src/right_gauge_cluster.cpp\n    src/cylinder_temperature_gauge.cpp\n    src/labeled_gauge.cpp\n    src/throttle_display.cpp\n    src/afr_cluster.cpp\n    src/fuel_cluster.cpp\n    src/oscilloscope_cluster.cpp\n    src/performance_cluster.cpp\n    src/firing_order_display.cpp\n    src/load_simulation_cluster.cpp\n    src/mixer_cluster.cpp\n    src/info_cluster.cpp\n\n    # Include files\n    include/delta.h\n    include/dtv.h\n    include/engine_sim_application.h\n    include/geometry_generator.h\n    include/simulation_object.h\n    include/piston_object.h\n    include/connecting_rod_object.h\n    include/ui_element.h\n    include/ui_manager.h\n    include/cylinder_pressure_gauge.h\n    include/ui_math.h\n    include/units.h\n    include/crankshaft_object.h\n    include/cylinder_bank_object.h\n    include/cylinder_head_object.h\n    include/ui_button.h\n    include/ui_utilities.h\n    include/combustion_chamber_object.h\n    include/oscilloscope.h\n    include/shaders.h\n    include/engine_view.h\n    include/right_gauge_cluster.h\n    include/cylinder_temperature_gauge.h\n    include/labeled_gauge.h\n    include/throttle_display.h\n    include/afr_cluster.h\n    include/fuel_cluster.h\n    include/oscilloscope_cluster.h\n    include/performance_cluster.h\n    include/firing_order_display.h\n    include/load_simulation_cluster.h\n    include/mixer_cluster.h\n    include/info_cluster.h\n)\n\ntarget_link_libraries(engine-sim-app\n    engine-sim\n)\n\nif (DTV)\n    target_link_libraries(engine-sim-app\n        direct-to-video)\nendif (DTV)\n\nif (PIRANHA_ENABLED)\n    target_link_libraries(engine-sim-app\n        engine-sim-script-interpreter)\nendif (PIRANHA_ENABLED)\n\nif (DISCORD_ENABLED)\n    add_library(discord-rpc STATIC IMPORTED)\n    set_property(TARGET discord-rpc PROPERTY IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/dependencies/discord/lib/discord-rpc.lib)\n    target_link_libraries(engine-sim-app\n    \tdiscord\n\tdiscord-rpc)\nendif (DISCORD_ENABLED)\n\ntarget_include_directories(engine-sim-app\n    PUBLIC dependencies/submodules)\n\nadd_subdirectory(dependencies)\n\n# GTEST\n\nenable_testing()\n\nadd_executable(engine-sim-test\n    # Source files\n    test/gas_system_tests.cpp\n    test/function_test.cpp\n    test/synthesizer_tests.cpp\n)\n\ntarget_link_libraries(engine-sim-test\n    gtest_main\n    engine-sim\n)\n\ninclude(GoogleTest)\ngtest_discover_tests(engine-sim-test)\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2022 AngeTheGreat (Ange Yaghi)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Engine Simulator\n![Alt text](docs/public/screenshots/screenshot_v01.png?raw=true)\n---\n# Engine Simulator has moved!\n\nTo get the newest releases of the game, [click here](https://github.com/Engine-Simulator/engine-sim-community-edition).\n---\n\n## What is this?\n\nThis is a real-time internal combustion engine simulation **designed specifically to produce engine audio and simulate engine response characteristics.** It is NOT a scientific tool and cannot be expected to provide accurate figures for the purposes of engineering or engine tuning.\n\n## How do I install it?\n\nThis is a code repository and might not look like other software that you're used to downloading and installing (if you're not familiar with programming). To download a ready-to-use version of the application, navigate to the [releases page](https://github.com/ange-yaghi/engine-sim/releases), find the most recent release (ex. `v0.1.5a`), click \"Assets\" and download the .zip file with a name that starts with `engine-sim-build`. Unzip this file, then run `bin/engine-sim-app.exe`. The simulator should then start normally.\n\nCheck out [our Frequently Asked Questions](https://github.com/ange-yaghi/engine-sim/wiki/Frequently-Asked-Questions) if you need more details.\n\n## How do I use it?\n\nThe UI is extremely minimalistic and there are only a few controls used to interact with the engine:\n\n| Key/Input | Action |\n| :---: | :---: |\n| A | Toggle ignition |\n| S | Hold for starter |\n| D | Enable dyno |\n| H | Enable RPM hold (see below for instructions) |\n| G + Scroll | Change hold speed |\n| F | Enter fullscreen mode |\n| I | Display dyno stats in the information panel |\n| Shift | Clutch (hold spacebar at the same time to slowly engage/disengage) |\n| Up Arrow | Up Gear | \n| Down Arrow | Down Gear | \n| Z + Scroll | Volume |\n| X + Scroll | Convolution Level |\n| C + Scroll | High frequency gain |\n| V + Scroll | Low frequency noise |\n| B + Scroll | High frequency noise |\n| N + Scroll | Simulation frequency |\n| M | Increase view layer |\n| , | Decrease view layer |\n| Enter | Reload engine script |\n| Escape | Exit the program |\n| Q, W, E, R | Change throttle position |\n| Space + Scroll | Fine throttle adjustment |\n| 1, 2, 3, 4, 5 | Simulation time warp |\n| Tab | Change screen |\n\n### Using the RPM hold\nThe RPM hold feature will hold the engine at a specific RPM and also measure the engine's horsepower and torque at that RPM. You can enable RPM hold by pressing the `H` key. **You must then enable the dynomometer** (press the `D` key) in order for the RPM hold to take effect. To change the hold speed, hold the `G` key and scroll with the mouse wheel. The RPM hold will be shown on the `DYNO. SPEED` gauge in the lower left of the screen.\n\n## Why is the code so sloppy?\n\nI wrote this to demo in a [YouTube video](https://youtu.be/RKT-sKtR970), not as a real product. If you would like it to become a usable product please reach out to me or join my Discord (link can be found in the description of the aforementioned YouTube video). I use this codebase for my own purposes and so it might change frequently and without warning.\n\n## How do I build it? (Ignore this section if you're not a developer!)\n**Note: this project currently only builds on Windows!**\n\n### Step 1 - Clone the repository\n```git clone --recurse-submodules https://github.com/ange-yaghi/engine-sim```\n\n### Step 2 - Install CMake\nInstall the latest version of CMake [here](https://cmake.org/) if it's not already installed.\n\n### Step 3 - Install Dependencies\nYou will need to install the following dependencies and CMake will need to be able to locate them (ie. they need to be listed on your PATH):\n\n    1. SDL2\n    2. SDL2_image\n    3. Boost (make sure to build the optional dependencies)\n    4. Flex and Bison\n\n### Step 4 - Build and Run\nFrom the root directory of the project, run the following commands:\n\n```\nmkdir build\ncd build\ncmake ..\ncmake --build .\n```\n\nIf these steps are successful, a Visual Studio solution will be generated in ```build```. You can open this project with Visual Studio and then run the ```engine-sim-app``` project. If you encounter an error telling you that you're missing DLLs, you will have to copy those DLLs to your EXE's directory.\n\n## Patreon Supporters\n\nThis project was made possible by the generous donations of the following individuals!\n\n### Grease Monkeys\n\n|<!-- -->|<!-- -->|<!-- -->|<!-- -->|<!-- -->|\n|-|-|-|-|-|\n|Devin@Hondatuningsuite|nut|Devin C Martinez|WelcomeCat|Saints Sasha|\n|Ida 8858|Emily|Steelorse |Kruddy|Sgt. Fluff|\n|darcuter|FatFluffyFox|Benton1234|Jim C K Flaten|The Zuck|\n|Blade Skydancer|Ye' old apple|Hayden Henderson|AlphaX|Lucas Martins Bündchen|\n|Jay Dog|damo|IBS-IS-CRAP|Snowy|Noah Greenberg|\n|Eisberg|Brendan M.|Alex Layton|Lukas Bartee|Thibaut Dubuisson|\n|The Cheeze Ity|JoeJimTom|MichaelB450|Björn|Bartdavy|\n|sasha bandelier|Caleb Black|COOKIES|Andrew Cooper|asimo3089|\n|Vim Wizard|Kevin Arsenault|Carl Linden|Kele Tappi|Kroklethon|\n|labourateur|viperfan7|SlimmyJimmy|Jason Becker|Sascha Kamp|\n|ves|Supernalboot |BeamNG|Paul Harrison|Tyler Russell (Nytelife26)|\n|nicholas jacobs|DrDotMadness|AVeryPlainTyler|Zach Perez|Paul Schaefer|\n|Clay Bauer|CR33DYM0N14|julien nadeau|Patt313|Philip Edwards|\n|RegularRuby670|Mateusz Ładosz|FémLol Stúdió|Crazy Yany|Elden|\n|Tristan Walker|Matthew McDonald|Jan-Sander Huiting|Mitchell Almstedt|Dylan Lebiedz|\n|Name Here|LoganBoi FNAF|Epic Randomness|MrPiThon|mike |\n|dung|Alvaro ArroyoZamora|Skinna Godwin|BeppoBarone|レナVA|\n|Sabata |Brady Fulham|Powerpuncher |NK10K|Gavin Osowski|\n|Orbitstrider|Steven Doyle|Jaksu2696|Toni |Devin Abolins|\n### Tuners\n\n|<!-- -->|<!-- -->|<!-- -->|<!-- -->|<!-- -->|\n|-|-|-|-|-|\n|Boosted Media|Matthew McLennan|Venican|Lyan le Golmuth|Alberto R.|\n|BetaToaster|Akira Takemoto|J Anderson|Apolly007|LexLuther|\n|xilophor|Robert K|viktor lind|Adrian Kucinski|sarowie .|\n|Chris Fischer|Marlod|Chase Hansen|Aidan Szalanski|Andrew Taylor|\n|Jason Hwang|Juuso Natunen|Ian Moss|PickleRick |Beljim46|\n|RSOFT92|UCD|Sped|OldManJenkins|James Hart|\n|Kalle Nilsson|XxBrasta455xX |Colin Sandage|Dakota Mackinnon|Carter Kopp|\n|Jakub Kozak|CJ Plessas|Loizeau|Charles Mills|YellowLight|\n|Didrik Esbjug|Alessandro Dal Pino|Carter Williams|Robert D|Cadence Plume|\n|BLANK|Provenance EMU|Dylan Engler|Nathan Rojas|Cornelius|\n|Acid|larsloveslegos|Maxime Desages|GM|BreadForMen|\n|Devin Freeman|Lieven RYCKEBOER|Amelia Taylor|Jelle Plukker|sodmo |\n|Jimmy Briscoe|Cirithor|Martin .K|DMartland|Lucas Diem|\n|Richard Budíček|Jack Sheppeard|Meemen|Anderson Huynh|NPException|\n|Mattia Villa|C|AIDAN POWELL|Brenn_the_Otter|Lane Mosier|\n|Ceze |oranjest1|Jw|ISON |Mathew Graham|\n|MACHINA|John Crowell|Asher Blythe|Cronos Skies|Matt Amott|\n|CpTKugelHagel|Simon Krayer|Caleb Bek|Monster Man25|GeneralMoineau|\n|EsuKurimu|Caleb Sartin|Jared L.|Hunter Wood|Ben Poole|\n|Steven Victoria|Jordan Zondlak|Agelessgod|Christopher Fahs|Jonathan Vincent|\n|Dalton Guillot|Simon Stojanovic|Andrew Urbanczyk|deniaL|Tyler Hughes|\n|vPam |Justin Kruithof|Curtis C Coomber|Sawyer Clark|Mike Hart|\n|Ciro Rancourt|Miles Guo|Rewind |E=mc^2|Keaton Call|\n|J.Es|Jeremy B|Chance Hall|Jack Tompkins|Race Sim Studio|\n|Quentin ZAOUI|Floyd Henderson|James Haylow|Milkshiekh |Wyatt Todd|\n|User 2820|Leon Schutte|CYBERBUG_JR|sebiii|Keegan|\n|Victor Cosiuga|Rolly !|Elias Pettersson|Tyson Makovec|Bill McDermott|\n|Phontonic|Simon Armstrong|avec |KidozyGAME (Dead)|Stephan Cote|\n|Justin Biggerstaff|Jabba Jubba|notD34THNIGHT|Inventor|Wesley Bear|\n|Supersonic2510|Pixel|Simon Bernhardt|Bas Vangermeersch|ToyotaCipra|\n|kyle crawford|ApatheticWood|Ben Vaughn|Erich Westhoven|Zack Myers|\n|Tbjoern|Vetle Høgås|Derek Thom|Aaron Beck||\n### Junior Mechanics\n\n|<!-- -->|<!-- -->|<!-- -->|<!-- -->|<!-- -->|\n|-|-|-|-|-|\n|Karol Szép|Leon Jordan|Nathan Higginson|Patrick F|Samuel Picard|\n|Alexander Fritsch|Lucas Scarpi|Jack Humbert|G2Eneko|SweCreations|\n|Marius Becker|Cedric Wille|infernap12 |Julian Dinges|Wamuthas|\n|Alex Mason|Hawar Karem|Melonenstrauch|Jacek Dębski|Alex Eastman|\n|Darren Taing|Po Wang|Giorgio Iannucci|Levis|Eden|\n|Alin Chiparatu|Arjun Mandakath|A.M. |Dylan Ryan|Noah Entrekin|\n|GT130|Josh D|generic|Henrik Cohrs|Nic Yetter|\n|Dan Fredriksen|153AN1MJ|Rasmus|EpicEcho|Kaur Hendrikson|\n|Maddox Partridge|L33TIFY_|Zack Fletcher|teiiio|Mike Zaite|\n|Evan Sonin|Christopher Zimmerman|PrefacedVase |funtomr|Triton Alabaster|\n|appelpie|Julien Ferluc|AnomalousFerret_|Miles Orozco|Spencer Teeter|\n|ThatCanadian|Harry Prabowo|Dylan Rogerson|Jaedyn Allen|Zephyr Sefira|\n|Alexander Stone|Mason Little|Wojciech Czop|ryzen5 |Kosta Diamantis|\n|Karol Stodolak|Tim van der Linde|Loïc Ruttner|jonthefuzz|AsgarK|\n|James Morgan|Elijah |1ntl|Tobias Johansson|Mome |\n|P|SOPA_|Shingekuro|Sean King|Russell Marsh|\n|Alyx Ranas|Naters305 |ChrisakaMrXD |Nic |sean|\n|Zach Hagedorn|Jhon lenon|Everett Butts|Kyan|ranger Nation|\n|Hiago Oliveira|Texi|MrRhody|Inglorious Bastard|Marty Mitchell|\n|Justin Chao|ManuelS|Cornelius Rössing|Pedro Freire|Anthony Stuart|\n|Hubba Nubba|Skychii|Joe Underwood|Xander_|Notbigdank|\n|Sander D.|Lars Joosten|Danksa|Metrostation |Myles Wommack|\n|Derrick Sampson|Corey Hannen|Matteo La Corte|Octothorp Obelus|David Baril|\n|Soyuz Kafire|Ivan Coha|BigElbowski|Apolepth|Julian Krad|\n|David Soulieres|Eric Huang|Léo Vias|Riccardo Mariani|Vic Viper|\n|Shinkaaaa|Mumaransa |Michael Banovsky|Hendrik Voss|Inverted Blackhat|\n|skipyC |Tobias Moor|jaky3 .|Clément LEGRAND|Ian C. Simpson|\n|Challier|Jan Przemysław Drabik|Dsand23|Smooth DLX|The German Dude|\n|CrazyEagle |Jordon Goodman|HenryWithaG .|Oscar Krula|Brayden Moore|\n|Steven|Nall Wolfert|papajonk|Andrew|Ben Kingston|\n|Julian Vogl|Maxime Lubrano|MrMekouil|Doudimme|Jacob Hultberg|\n|Nolan Orloff|Mike|tobi9899 |Danila Frolkin|Xecotcovach|\n|Aj|Carcar404|John Martin|Dominik Greinert|Lukas Stadler|\n|Oliver Yang|sonax51|Marcel Kliment|Chris|David Rush|\n|LethalVenom13|Dave Osterhoff|Anto1709|Ben|Morgan Munroe|\n|Ivor Forrest|Sam Hopkins|Atte |Dax |William Bergström|\n|homelessmeme|Thanleft|Zaxerg |Robeloox|Maximilian-Lukas Marz|\n|Morgn|Seth Monteleone|playfulmean videos|Lanimations LA|Bram G|\n|Benoit Fournier|Bernar Lepiller|Nicolas Baur|the |Snekers|\n|Darkmount|Tobiasz Michalik|Aidas Ri|Daniel Postler|Skim_Beeblez|\n|Impetus|Thunderbird324|Fred Joss|Krzysztof Radowski|Azerrty|\n|Harrison Speck|Matt Baker|BigLynch|Markus Pelto|IMBIBE|\n|James L Plummer|Rose Giles|Jonas Brekka|HASTRX|Lepoucehumain|\n|Naomi Humin|qkrrudgks|Johann Gross|Janis Knappich|WhatTheDuck|\n|테루|Glimple Bort|Jacob Tudisco|Tanner|Julian kaspi|\n|nathan gould|Randal Rainis Kruus|Beppierre|Craig Martin|Thomas Bukovsky|\n|Colaxe|Robert Oram|Matsuy15 L|Kacpe|Alex Sedlic|\n|Mark Benson|Mhenn!|Anders Nelson|Dingus|Rustle|\n|Marco Schulz|stratum |brochier gabriel|Thomas|brody of hillcountry|\n|Thomas Afford|Brody Blaskie|Martien Gaming|Adrien MC|William A Grubbs|\n|Trevo Ph.D.|Donovan Gibson|Polish R3t4rd|Keith Price|LAWL CAKE|\n|Rhien Schultz|FireThrow13|Seraphim|Titus Standing|Matt Miklos|\n|B Dub|Jonathan Ekman|Al Pomeroy|Vestii|Wil|\n|adrian|Airatise|TJ Sinkoski|Shotts SilverStone|Reagan Carbaugh|\n|WarAestheticsRebooted|Aidan Case|Casey Bryant Goodwin|Konrad|Adam Larcher|\n|Kazar Xin Xiao|Riccardo Marcaccio|William S.|Francis Filion|Loïc |\n|Kenny Deane|Blackspots|mike |MXT|Joshua Gibson|\n|milky boi|Hagen|gunmaster929 |jgvan |Benny 282|\n|Sean Wehner|Christian Poole|Ethan|Tsukiyama Shuu|Ooof_uhhh_haah|\n|sano ken ch|Diego Martinez|Chuck|GalaxyFrogs|TheGeForce |\n|Chriphost|Carthage|Greg L|Chipskate|Muhammed Mehmood|\n|Hamilton Sjoberg|Amina Moh|vSiiFT|Jeremy Wren|Esteban Acosta|\n|John A Ullenberg|Michael Morozov|Andrew Webberley|Nathaniel Lim|Aaron Ksicinski|\n|Apocalypt|Josh batuzich|Ed|Hunter |Gene Brockoff|\n|Redheadspellslinger.|Pablo Magariños|Nilz|Jose Manuel Silva Calvo|AJ|\n|Ethan Wille |Aurora|Derek Shunia|Jan|Nope Mircea|\n|Giancarlo Cestari|Tanner Edge|brad.|Connor Merrick|Martin Scholer|\n|Deppy|Dan Smith|Tyson |Jac Comeau|Itemfinder |\n|Tischer Games|Pedro Henrique|BeenWashedUp|martin wolff|Kurt Houben|\n|Thomas Onslow|Brendan Puglisi|Kai Anquetil|Rudolph Ignatenko|CloudHackIX|\n|Zach Carreau|Jonathan Vanderlyn|Krobivnov|ienergy|Leifster|\n|Mikael Kaaronen|Glen|H.Helsing|ange|The Nobles|\n|Johnathan Johnson|Juha Merentie|Jim Fares|Tom Marshall|Superferrariman|\n|Zakary Zisa|JustTy|晟道 杜|Dnialibr Williams|Takumi Fujiwara|\n|Koen van Hal|Jonathan Hill|Marco Siciliano|Kevindosenfutter|Angry Prawn|\n|Natharic 67|Rafael Monteiro|Jacob Ashline|ChironTheFloof|Caleb Dauphinee|\n|Tony |Zac L|AlainMoto FPV|eirik johan johnsen|Elderet|\n|Miles Longmore|lemon head|Viccy|Casey|Kajetan Cupa|\n|Conejero00|Bill Gricko|A cow wearing a turban|Danni Nowicki|Udo Schmidt|\n|Tyler Swords|Constellation Gaming|Manimo|valentine|Jules Schattenberg|\n|Brandon Crotts|Philipp Popetschnigg|Tiziano Della Fazia|goodgamer1109|Joshua Thomas|\n|Jeff Testa|Avery Snyder|Josh Kern|Triptagram|Bayon Antoine|\n|Iván Juárez Núñez|Amery Martinat|ElArGee|Cory Green|lucas Di lorenzo|\n|Caleb Sandersier|||||\n"
  },
  {
    "path": "assets/engines/atg-video-1/01_honda_trx520.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n}\n\npublic node honda_trx520 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Honda TRX520 (ATV)\",\n        starter_torque: 50 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 5000 * units.rpm,\n        fuel: fuel(\n            max_burning_efficiency: 1.0\n        ),\n        hf_gain: 0.00121,\n        noise: 0.229,\n        jitter: 0.42,\n        simulation_frequency: 40000\n    )\n\n    wires wires()\n\n    crankshaft c0(\n        throw: 71.5 * units.mm / 2,\n        flywheel_mass: 5 * units.lb,\n        mass: 5 * units.lb,\n        friction_torque: 5.0 * units.lb_ft,\n        moment_of_inertia: 0.22986844776863666 * 0.2,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: constants.pi / 2\n    )\n\n    rod_journal rj0(angle: 0.0)\n    c0\n        .add_rod_journal(rj0)\n\n    piston_parameters piston_params(\n        mass: 100 * units.g,\n        compression_height: 1.0 * units.inch,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: 100.0 * units.g,\n        moment_of_inertia: 0.0015884918028487504,\n        center_of_mass: 0.0,\n        length: 6.0 * units.inch\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: 96 * units.mm,\n        deck_height: 71.5 * units.mm / 2 + 6.0 * units.inch + 1.0 * units.inch\n    )\n\n    intake intake(\n        plenum_volume: 1.5 * units.L,\n        plenum_cross_section_area: 10.0 * units.cm2,\n        intake_flow_rate: k_carb(100.0),\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.993,\n        velocity_decay: 0.5\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(500.0),\n        primary_tube_length: 20.0 * units.inch,\n        primary_flow_rate: k_carb(200.0),\n        velocity_decay: 0.5,\n        volume: 5.0 * units.L\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 1.0,\n        impulse_response: ir_lib.mild_exhaust_0_reverb\n    )\n\n    cylinder_bank b0(bank_params, angle: 0 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe lobe(\n        duration_at_50_thou: 180 * units.deg,\n        gamma: 1.0,\n        lift: 200 * units.thou,\n        steps: 100\n    )\n\n    vtwin90_camshaft_builder camshaft(\n        lobe_profile: lobe,\n        lobe_separation: 100 * units.deg,\n        base_radius: 500 * units.thou\n    )\n\n    b0.set_cylinder_head (\n        generic_small_engine_head(\n            chamber_volume: 60 * units.cc,\n            intake_camshaft: camshaft.intake_cam_0,\n            exhaust_camshaft: camshaft.exhaust_cam_0,\n            flow_attenuation: 2.0,\n            intake_runner_cross_section_area: 9.0 * units.cm2,\n            exhaust_runner_cross_section_area: 9.0 * units.cm2\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(1000 * units.rpm, 12 * units.deg)\n        .add_sample(2000 * units.rpm, 20 * units.deg)\n        .add_sample(3000 * units.rpm, 35 * units.deg)\n        .add_sample(4000 * units.rpm, 35 * units.deg)\n\n    engine.add_ignition_module(\n        single_cylinder_distributor(\n            wires: wires,\n            timing_curve: timing_curve,\n            rev_limit: 6000 * units.rpm\n        ))\n}\n\nprivate node honda_trx520_transmission {\n    alias output __out: transmission;\n\n    transmission transmission(\n        max_clutch_torque: 50 * units.lb_ft\n    )\n\n    transmission.add_gear(4.0)\n    transmission.add_gear(3.5)\n    transmission.add_gear(3.0)\n    transmission.add_gear(2.5)\n    transmission.add_gear(2.0)\n}\n\nprivate node honda_trx520_vehicle {\n    alias output __out:\n        vehicle(\n            mass: 500 * units.kg,\n            drag_coefficient: 0.25,\n            cross_sectional_area: (47 * units.inch) * (47 * units.inch),\n            diff_ratio: 3.33,\n            tire_radius: 11 * units.inch,\n            rolling_resistance: 200 * units.N\n        );\n}\n\npublic node main {\n    set_engine(honda_trx520())\n    set_transmission(honda_trx520_transmission())\n    set_vehicle(honda_trx520_vehicle())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-1/02_kohler_ch750.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n}\n\npublic node kohler_ch750 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Kohler CH750\",\n        starter_torque: 50 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 3600 * units.rpm,\n        throttle:\n            governor(\n                min_speed: 1600 * units.rpm,\n                max_speed: 3500 * units.rpm,\n                min_v: -5.0,\n                max_v: 5.0,\n                k_s: 0.0006,\n                k_d: 200.0,\n                gamma: 2.0\n            ),\n        hf_gain: 0.01,\n        noise: 1.0,\n        jitter: 0.5,\n        simulation_frequency: 30000\n    )\n\n    wires wires()\n\n    crankshaft c0(\n        throw: 69 * units.mm / 2,\n        flywheel_mass: 5 * units.lb,\n        mass: 5 * units.lb,\n        friction_torque: 10.0 * units.lb_ft,\n        moment_of_inertia: 0.22986844776863666 * 0.5,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: constants.pi / 4\n    )\n\n    rod_journal rj0(angle: 0.0)\n    c0\n        .add_rod_journal(rj0)\n\n    piston_parameters piston_params(\n        mass: 400 * units.g,\n        //blowby: k_28inH2O(0.1),\n        compression_height: 1.0 * units.inch,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: 300.0 * units.g,\n        moment_of_inertia: 0.0015884918028487504,\n        center_of_mass: 0.0,\n        length: 4.0 * units.inch\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: 83 * units.mm,\n        deck_height: (4.0 + 1) * units.inch + 69 * units.mm / 2\n    )\n\n    intake intake(\n        plenum_volume: 1.0 * units.L,\n        plenum_cross_section_area: 10.0 * units.cm2,\n        intake_flow_rate: k_carb(50.0),\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.96\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(300.0),\n        primary_tube_length: 10.0 * units.inch,\n        primary_flow_rate: k_carb(200.0),\n        velocity_decay: 1.0,\n        volume: 20.0 * units.L\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 1.0,\n        impulse_response: ir_lib.default_0\n    )\n\n    cylinder_bank b0(bank_params, angle: -45 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1\n        )\n\n    cylinder_bank b1(bank_params, angle: 45.0 * units.deg)\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire2\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe lobe(\n        duration_at_50_thou: 160 * units.deg,\n        gamma: 1.1,\n        lift: 200 * units.thou,\n        steps: 100\n    )\n\n    vtwin90_camshaft_builder camshaft(\n        lobe_profile: lobe,\n        lobe_separation: 114 * units.deg,\n        base_radius: 500 * units.thou\n    )\n\n    b0.set_cylinder_head (\n        generic_small_engine_head(\n            chamber_volume: 50 * units.cc,\n            intake_camshaft: camshaft.intake_cam_0,\n            exhaust_camshaft: camshaft.exhaust_cam_0\n        )\n    )\n    b1.set_cylinder_head (\n        generic_small_engine_head(\n            chamber_volume: 50 * units.cc,\n            intake_camshaft: camshaft.intake_cam_1,\n            exhaust_camshaft: camshaft.exhaust_cam_1,\n            flip_display: true\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 18 * units.deg)\n        .add_sample(1000 * units.rpm, 30 * units.deg)\n        .add_sample(2000 * units.rpm, 50 * units.deg)\n        .add_sample(3000 * units.rpm, 50 * units.deg)\n        .add_sample(4000 * units.rpm, 50 * units.deg)\n\n    engine.add_ignition_module(\n        vtwin90_distributor(\n            wires: wires,\n            timing_curve: timing_curve,\n            rev_limit: 5000 * units.rpm\n        ))\n}\n\nprivate node direct_drive {\n    alias output __out: transmission;\n\n    transmission transmission(\n        max_clutch_torque: 35 * units.lb_ft\n    )\n\n    transmission.add_gear(1.0)\n}\n\nprivate node static_load {\n    alias output __out:\n        vehicle(\n            mass: 563 * units.lb,\n            drag_coefficient: 0.1,\n            cross_sectional_area: (20 * units.inch) * (47 * units.inch),\n            diff_ratio: 2.353,\n            tire_radius: 8.5 * units.inch,\n            rolling_resistance: 10000 * units.N\n        );\n}\n\npublic node main {\n    set_engine(kohler_ch750())\n    set_transmission(direct_drive())\n    set_vehicle(static_load())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-1/03_harley_davidson_shovelhead.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n}\n\npublic node harley_distributor {\n    input wires;\n    input timing_curve;\n    input rev_limit: 5500 * units.rpm;\n    alias output __out:\n        ignition_module(timing_curve: timing_curve, rev_limit: rev_limit)\n            .connect_wire(wires.wire1, 0 * units.deg)\n            .connect_wire(wires.wire2, 315 * units.deg);\n}\n\npublic node harley_davidson_shovelhead {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Harley Davidson Shovelhead\",\n        starter_torque: 70 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 5000 * units.rpm,\n        hf_gain: 0.01,\n        noise: 0.115,\n        jitter: 0.136,\n        simulation_frequency: 35000\n    )\n\n    wires wires()\n\n    label stroke(4.25 * units.inch)\n    label bore(3.5 * units.inch)\n    label rod_length(8 * units.inch)\n    label compression_height(1.0 * units.inch)\n    label crank_mass(9.39 * units.kg)\n    label flywheel_mass(15 * units.kg)\n    label flywheel_radius(6 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 10 * units.kg, radius: 3.0 * units.cm)\n    )\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 5.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: (90 - 0.5 * 45) * units.deg\n    )\n\n    rod_journal rj0(angle: 0.0)\n    c0\n        .add_rod_journal(rj0)\n\n    piston_parameters piston_params(\n        mass: 500 * units.g,\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: 500.0 * units.g,\n        moment_of_inertia: 0.0015884918028487504,\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 1.5 * units.L,\n        plenum_cross_section_area: 10.0 * units.cm2,\n        intake_flow_rate: k_carb(100.0),\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.991,\n        throttle_gamma: 1.0,\n        velocity_decay: 1.0\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(100.0),\n        primary_tube_length: 70.0 * units.inch,\n        primary_flow_rate: k_carb(100.0),\n        velocity_decay: 0.75,\n        volume: 10.0 * units.L\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 1.0 * 0.1,\n        impulse_response: ir_lib.minimal_muffling_01\n    )\n\n    exhaust_system exhaust1(\n        es_params,\n        audio_volume: 2.0 * 0.1,\n        impulse_response: ir_lib.minimal_muffling_01\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    cylinder_bank b0(bank_params, angle: -0.5 * 45 * units.deg, display_depth: 0.55)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1\n        )\n\n    cylinder_bank b1(bank_params, angle: 0.5 * 45 * units.deg, display_depth: 0.55)\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe lobe(\n        duration_at_50_thou: 210 * units.deg,\n        gamma: 0.9,\n        lift: 400 * units.thou,\n        steps: 100\n    )\n\n    vtwin_camshaft_builder camshaft(\n        lobe_profile: lobe,\n        lobe_separation: 110 * units.deg,\n        base_radius: 500 * units.thou,\n        angle: 315 * units.deg\n    )\n\n    b0.set_cylinder_head (\n        generic_small_engine_head(\n            chamber_volume: 100 * units.cc,\n            intake_camshaft: camshaft.intake_cam_0,\n            exhaust_camshaft: camshaft.exhaust_cam_0,\n            flow_attenuation: 2.0,\n            intake_runner_cross_section_area: 20.0 * units.cm2,\n            exhaust_runner_cross_section_area: 20.0 * units.cm2\n        )\n    )\n\n    b1.set_cylinder_head (\n        generic_small_engine_head(\n            flip_display: true,\n            chamber_volume: 100 * units.cc,\n            intake_camshaft: camshaft.intake_cam_1,\n            exhaust_camshaft: camshaft.exhaust_cam_1,\n            flow_attenuation: 1.0,\n            intake_runner_cross_section_area: 20.0 * units.cm2,\n            exhaust_runner_cross_section_area: 20.0 * units.cm2\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 18 * units.deg)\n        .add_sample(1000 * units.rpm, 18 * units.deg)\n        .add_sample(2000 * units.rpm, 30 * units.deg)\n        .add_sample(3000 * units.rpm, 40 * units.deg)\n        .add_sample(4000 * units.rpm, 40 * units.deg)\n\n    engine.add_ignition_module(\n        harley_distributor(\n            wires: wires,\n            timing_curve: timing_curve,\n            rev_limit: 5500 * units.rpm\n        ))\n}\n\nprivate node harley_davidson_motorcyle {\n    alias output __out:\n        vehicle(\n            mass: 900 * units.lb,\n            drag_coefficient: 0.1,\n            cross_sectional_area: (15 * units.inch) * (47 * units.inch),\n            diff_ratio: 2.0,\n            tire_radius: 11 * units.inch,\n            rolling_resistance: 200 * units.N\n        );\n}\n\nprivate node harley_davidson_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 200 * units.lb_ft\n        )\n        .add_gear(3.34)\n        .add_gear(2.30)\n        .add_gear(1.71)\n        .add_gear(1.41)\n        .add_gear(1.18)\n        .add_gear(1.00);\n}\n\npublic node main {\n    set_engine(harley_davidson_shovelhead())\n    set_vehicle(harley_davidson_motorcyle())\n    set_transmission(harley_davidson_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-1/04_hayabusa.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\nlabel cycle(2 * 360 * units.deg)\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n}\n\nprivate node hayabusa_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 19.2 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 10.0 * units.cm2 * 2.0;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 15.0 * units.cm2 * 2.0;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 40 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 80 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 125 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 190 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 210 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 225 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 230 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 240 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 30 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 70 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 100 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 125 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 140 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 150 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 165 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 170 * flow_attenuation)\n\n    cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        intake_camshaft: intake_camshaft,\n        exhaust_camshaft: exhaust_camshaft,\n        flip_display: flip_display\n    )\n}\n\nprivate node hayabusa_camshaft {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;\n    input advance: 0 * units.deg; \n    input base_radius: 0.75 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output exhaust_cam_0: _exhaust_cam_0;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot180(180 * units.deg)\n    label rot360(360 * units.deg)\n\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center + (0.0 / 4) * cycle)\n        .add_lobe(rot360 - exhaust_lobe_center + (1.0 / 4) * cycle)\n        .add_lobe(rot360 - exhaust_lobe_center + (3.0 / 4) * cycle)\n        .add_lobe(rot360 - exhaust_lobe_center + (2.0 / 4) * cycle)\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center + (0.0 / 4) * cycle)\n        .add_lobe(rot360 + intake_lobe_center + (1.0 / 4) * cycle)\n        .add_lobe(rot360 + intake_lobe_center + (3.0 / 4) * cycle)\n        .add_lobe(rot360 + intake_lobe_center + (2.0 / 4) * cycle)\n}\n\npublic node hayabusa_i4 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Suzuki Hayabusa I4\",\n        starter_torque: 70 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 11000 * units.rpm,\n        fuel: fuel(\n            max_turbulence_effect: 5.5,\n            max_dilution_effect: 1000.0,\n            max_burning_efficiency: 1.0,\n            burning_efficiency_randomness: 0.0\n        ),\n        throttle_gamma: 2.0,\n        hf_gain: 0.00407,\n        noise: 0.292,\n        jitter: 0.062,\n        simulation_frequency: 20000\n    )\n\n    wires wires()\n\n    label stroke(65 * units.mm)\n    label bore(81 * units.mm)\n    label rod_length(4.705 * units.inch)\n    label compression_height(1.0 * units.inch)\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: 10 * units.lb,\n        mass: 24.8 * units.lb,\n        friction_torque: 1.0 * units.lb_ft,\n        moment_of_inertia: 0.22986844776863666 * 0.2,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: 90 * units.deg\n    )\n\n    rod_journal rj0(angle: 0.0 * units.deg)\n    rod_journal rj1(angle: 180.0 * units.deg)\n    rod_journal rj2(angle: 180.0 * units.deg)\n    rod_journal rj3(angle: 0.0 * units.deg)\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n\n    piston_parameters piston_params(\n        mass: 303.5 * units.g,\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: 395.837 * units.g,\n        moment_of_inertia: 0.0015884918028487504,\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 4.5 * units.L,\n        plenum_cross_section_area: 10.0 * units.cm2,\n        intake_flow_rate: k_carb(800.0),\n        runner_flow_rate: k_carb(300.0),\n        runner_length: 10.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.999,\n        velocity_decay: 0.5\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(1000.0),\n        primary_tube_length: 40.0 * units.inch,\n        primary_flow_rate: k_carb(500.0),\n        velocity_decay: 1.0,\n        volume: 10.0 * units.L\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 1.0 * 0.25,\n        impulse_response: ir_lib.minimal_muffling_03\n    )\n\n    exhaust_system exhaust1(\n        es_params,\n        audio_volume: 2.0 * 0.25,\n        impulse_response: ir_lib.minimal_muffling_03\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    cylinder_bank b0(bank_params, angle: 0.0 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.001)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1,\n            sound_attenuation: 0.9\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.002)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2,\n            sound_attenuation: 0.8\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.001)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3,\n            sound_attenuation: 1.1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.002)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4,\n            sound_attenuation: 0.9\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 240 * units.deg,\n        gamma: 1.2,\n        lift: 345 * units.thou,\n        steps: 100\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 220 * units.deg,\n        gamma: 1.2,\n        lift: 294 * units.thou,\n        steps: 100\n    )\n\n    hayabusa_camshaft camshaft(\n        lobe_profile: \"N/A\",\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 105 * units.deg,\n        exhaust_lobe_center: 100 * units.deg,\n        base_radius: 500 * units.thou\n    )\n\n    b0.set_cylinder_head (\n        hayabusa_head(\n            intake_camshaft: camshaft.intake_cam_0,\n            exhaust_camshaft: camshaft.exhaust_cam_0\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 25 * units.deg)\n        .add_sample(1000 * units.rpm, 25 * units.deg)\n        .add_sample(2000 * units.rpm, 30 * units.deg)\n        .add_sample(3000 * units.rpm, 40 * units.deg)\n        .add_sample(4000 * units.rpm, 40 * units.deg)\n\n    ignition_module ignition_module(\n        timing_curve: timing_curve,\n        rev_limit: 11500 * units.rpm,\n        limiter_duration: 0.05)\n    ignition_module\n            .connect_wire(wires.wire1, (0.0 / 4.0) * cycle)\n            .connect_wire(wires.wire2, (1.0 / 4.0) * cycle)\n            .connect_wire(wires.wire4, (2.0 / 4.0) * cycle)\n            .connect_wire(wires.wire3, (3.0 / 4.0) * cycle)\n\n    engine.add_ignition_module(ignition_module)\n}\n\nprivate node hayabusa_transmission {\n    alias output __out: transmission;\n\n    transmission transmission(\n        max_clutch_torque: 200 * units.lb_ft\n    )\n\n    transmission.add_gear(2.615)\n    transmission.add_gear(1.937)\n    transmission.add_gear(1.526)\n    transmission.add_gear(1.285)\n    transmission.add_gear(1.136)\n    transmission.add_gear(1.043)\n}\n\nprivate node hayabusa {\n    alias output __out:\n        vehicle(\n            mass: 563 * units.lb,\n            drag_coefficient: 0.1,\n            cross_sectional_area: (20 * units.inch) * (47 * units.inch),\n            diff_ratio: 2.353,\n            tire_radius: 8.5 * units.inch,\n            rolling_resistance: 100 * units.N\n        );\n}\n\npublic node main {\n    set_engine(hayabusa_i4())\n    set_transmission(hayabusa_transmission())\n    set_vehicle(hayabusa())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-1/05_honda_vtec.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\nlabel cycle(2 * 360 * units.deg)\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n}\n\nprivate node honda_vtec_head {\n    input camshaft_set;\n    input chamber_volume: 41.6 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 1.35 * units.inch * 1.35 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 1.25 * units.inch * 1.25 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 50 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 80 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 125 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 190 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 210 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 225 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 230 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 250 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 50 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 80 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 110 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 130 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 150 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 170 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 170 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 170 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: vtec_valvetrain(\n            vtec_intake_camshaft: camshaft_set.vtec_intake_cam,\n            vtec_exhaust_camshaft: camshaft_set.vtec_exhaust_cam,\n            intake_camshaft: camshaft_set.intake_cam,\n            exhaust_camshaft: camshaft_set.exhaust_cam\n        ),\n        flip_display: flip_display\n    )\n}\n\nprivate node honda_vtec_camshaft {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n\n    input vtec_lobe_profile;\n    input vtec_intake_lobe_profile: vtec_lobe_profile;\n    input vtec_exhaust_lobe_profile: vtec_lobe_profile;\n\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;\n    input vtec_lobe_separation: 110 * units.deg;\n    input vtec_intake_lobe_center: vtec_lobe_separation;\n    input vtec_exhaust_lobe_center: vtec_lobe_separation;   \n    input advance: 0 * units.deg; \n    input base_radius: 1.0 * units.inch;\n\n    output intake_cam: _intake_cam;\n    output exhaust_cam: _exhaust_cam;\n    output vtec_intake_cam: _vtec_intake_cam;\n    output vtec_exhaust_cam: _vtec_exhaust_cam;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _vtec_intake_cam(params, lobe_profile: vtec_intake_lobe_profile)\n    camshaft _vtec_exhaust_cam(params, lobe_profile: vtec_exhaust_lobe_profile)\n\n    label rot180(180 * units.deg)\n    label rot360(360 * units.deg)\n\n    _exhaust_cam\n        .add_lobe(rot360 + exhaust_lobe_center - (0.0 / 4) * cycle)\n        .add_lobe(rot360 + exhaust_lobe_center - (3.0 / 4) * cycle)\n        .add_lobe(rot360 + exhaust_lobe_center - (1.0 / 4) * cycle)\n        .add_lobe(rot360 + exhaust_lobe_center - (2.0 / 4) * cycle)\n    _intake_cam\n        .add_lobe(rot360 - intake_lobe_center - (0.0 / 4) * cycle)\n        .add_lobe(rot360 - intake_lobe_center - (3.0 / 4) * cycle)\n        .add_lobe(rot360 - intake_lobe_center - (1.0 / 4) * cycle)\n        .add_lobe(rot360 - intake_lobe_center - (2.0 / 4) * cycle)\n    _vtec_exhaust_cam\n        .add_lobe(rot360 + vtec_exhaust_lobe_center - (0.0 / 4) * cycle)\n        .add_lobe(rot360 + vtec_exhaust_lobe_center - (3.0 / 4) * cycle)\n        .add_lobe(rot360 + vtec_exhaust_lobe_center - (1.0 / 4) * cycle)\n        .add_lobe(rot360 + vtec_exhaust_lobe_center - (2.0 / 4) * cycle)\n    _vtec_intake_cam\n        .add_lobe(rot360 - vtec_intake_lobe_center - (0.0 / 4) * cycle)\n        .add_lobe(rot360 - vtec_intake_lobe_center - (3.0 / 4) * cycle)\n        .add_lobe(rot360 - vtec_intake_lobe_center - (1.0 / 4) * cycle)\n        .add_lobe(rot360 - vtec_intake_lobe_center - (2.0 / 4) * cycle)\n}\n\npublic node honda_vtec_i4 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Honda B18C5 [VTEC, I4]\",\n        starter_torque: 70 * units.lb_ft,\n        starter_speed: -500 * units.rpm,\n        redline: 8400 * units.rpm,\n        fuel: fuel(\n            max_turbulence_effect: 2.5,\n            max_burning_efficiency: 0.75\n        ),\n        throttle_gamma: 2.0,\n        hf_gain: 0.002,\n        noise: 0.253,\n        jitter: 0.195,\n        simulation_frequency: 20000\n    )\n\n    wires wires()\n\n    label stroke(87.2 * units.mm)\n    label bore(81 * units.mm)\n    label rod_length(5.430 * units.inch)\n    label compression_height(1.0 * units.inch)\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: 10 * units.lb,\n        mass: 35.5 * units.lb,\n        friction_torque: 1.0 * units.lb_ft,\n        moment_of_inertia: 0.22986844776863666 * 0.5,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: 90 * units.deg\n    )\n\n    rod_journal rj0(angle: 0.0 * units.deg)\n    rod_journal rj1(angle: 180.0 * units.deg)\n    rod_journal rj2(angle: 180.0 * units.deg)\n    rod_journal rj3(angle: 0.0 * units.deg)\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n\n    piston_parameters piston_params(\n        mass: 303.5 * units.g,\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: 395.837 * units.g,\n        moment_of_inertia: 0.0015884918028487504,\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 1.325 * units.L,\n        plenum_cross_section_area: 20.0 * units.cm2,\n        intake_flow_rate: k_carb(800.0),\n        runner_flow_rate: k_carb(250.0),\n        runner_length: 7.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.9989,\n        velocity_decay: 0.5\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(1000.0),\n        primary_tube_length: 10.0 * units.inch,\n        primary_flow_rate: k_carb(200.0),\n        velocity_decay: 1.0,\n        volume: 100.0 * units.L\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 8 * 0.75,\n        impulse_response: ir_lib.mild_exhaust_0\n    )\n\n    exhaust_system exhaust1(\n        es_params,\n        audio_volume: 8 * 1.0,\n        impulse_response: ir_lib.mild_exhaust_0\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    cylinder_bank b0(bank_params, angle: 0.0 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.001)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1,\n            sound_attenuation: 0.9\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.002)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2,\n            sound_attenuation: 1.1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.001)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3,\n            sound_attenuation: 0.8\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.002)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4,\n            sound_attenuation: 0.9\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 210 * units.deg,\n        gamma: 1.0,\n        lift: 6.9 * units.mm,\n        steps: 100\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 190 * units.deg,\n        gamma: 1.0,\n        lift: 6.5 * units.mm,\n        steps: 100\n    )\n\n    harmonic_cam_lobe vtec_intake_lobe(\n        duration_at_50_thou: 240 * units.deg,\n        gamma: 0.5,\n        lift: 11.5 * units.mm,\n        steps: 100\n    )\n\n    harmonic_cam_lobe vtec_exhaust_lobe(\n        duration_at_50_thou: 232 * units.deg,\n        gamma: 0.5,\n        lift: 10.5 * units.mm,\n        steps: 100\n    )\n\n    honda_vtec_camshaft camshaft(\n        lobe_profile: \"N/A\",\n        vtec_lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        vtec_intake_lobe_profile: vtec_intake_lobe,\n        vtec_exhaust_lobe_profile: vtec_exhaust_lobe,\n        intake_lobe_center: 116 * units.deg,\n        exhaust_lobe_center: 116 * units.deg,\n        vtec_intake_lobe_center: 100 * units.deg,\n        vtec_exhaust_lobe_center: 100 * units.deg,\n        base_radius: 500 * units.thou\n    )\n\n    b0.set_cylinder_head (\n        honda_vtec_head(\n            camshaft_set: camshaft\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, -25 * units.deg)\n        .add_sample(1000 * units.rpm, -25 * units.deg)\n        .add_sample(2000 * units.rpm, -30 * units.deg)\n        .add_sample(3000 * units.rpm, -40 * units.deg)\n        .add_sample(4000 * units.rpm, -40 * units.deg)\n\n    ignition_module ignition_module(\n        timing_curve: timing_curve,\n        rev_limit: 9400 * units.rpm, //43244374\n        limiter_duration: 0.05)\n    ignition_module\n            .connect_wire(wires.wire1, (0.0 / 4.0) * cycle)\n            .connect_wire(wires.wire3, (3.0 / 4.0) * cycle)\n            .connect_wire(wires.wire4, (2.0 / 4.0) * cycle)\n            .connect_wire(wires.wire2, (1.0 / 4.0) * cycle)\n\n    engine.add_ignition_module(ignition_module)\n}\n\nprivate node integra_type_r {\n    alias output __out:\n        vehicle(\n            mass: 2400 * units.lb,\n            drag_coefficient: 0.2,\n            cross_sectional_area: (66 * units.inch) * (50 * units.inch),\n            diff_ratio: 3.55,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 300 * units.N\n        );\n}\n\nprivate node integra_type_r_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 300 * units.lb_ft\n        )\n        .add_gear(3.23)\n        .add_gear(2.105)\n        .add_gear(1.458)\n        .add_gear(1.107)\n        .add_gear(0.848);\n}\n\npublic node main {\n    set_engine(honda_vtec_i4())\n    set_vehicle(integra_type_r())\n    set_transmission(integra_type_r_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-1/06_subaru_ej25.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\nlabel cycle(2 * 360 * units.deg)\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n}\n\nprivate node ej25_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 67 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 1.35 * units.inch * 1.35 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 1.25 * units.inch * 1.25 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 58 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 103 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 156 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 214 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 249 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 268 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 281 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 37 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 72 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 113 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 196 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 222 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 245 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 246 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: standard_valvetrain(\n            intake_camshaft: intake_camshaft,\n            exhaust_camshaft: exhaust_camshaft\n        ),\n        flip_display: flip_display\n    )\n}\n\nprivate node ej25_camshaft {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;  \n    input advance: 0 * units.deg; \n    input base_radius: 1.0 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output exhaust_cam_0: _exhaust_cam_0;\n\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot180(180 * units.deg)\n    label rot360(360 * units.deg)\n\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center + (0.0 / 4) * cycle)\n        .add_lobe(rot360 - exhaust_lobe_center + (1.0 / 4) * cycle)\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center + (0.0 / 4) * cycle)\n        .add_lobe(rot360 + intake_lobe_center + (1.0 / 4) * cycle)\n\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + (2.0 / 4) * cycle)\n        .add_lobe(rot360 - exhaust_lobe_center + (3.0 / 4) * cycle)\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + (2.0 / 4) * cycle)\n        .add_lobe(rot360 + intake_lobe_center + (3.0 / 4) * cycle)\n}\n\npublic node subaru_ej25 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Subaru EJ25\",\n        starter_torque: 70 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 6500 * units.rpm,\n        fuel: fuel(\n            max_turbulence_effect: 2.5,\n            max_burning_efficiency: 0.75\n        ),\n        throttle_gamma: 2.0,\n        hf_gain: 0.01,\n        noise: 1.0,\n        jitter: 0.5,\n        simulation_frequency: 20000\n    )\n\n    wires wires()\n\n    label stroke(79 * units.mm)\n    label bore(99.5 * units.mm)\n    label rod_length(5.142 * units.inch)\n    label rod_mass(535 * units.g)\n    label compression_height(1.0 * units.inch)\n    label crank_mass(9.39 * units.kg)\n    label flywheel_mass(6.8 * units.kg)\n    label flywheel_radius(6 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 10 * units.kg, radius: 6.0 * units.cm)\n    )\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 1.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: 180 * units.deg\n    )\n\n    rod_journal rj0(angle: 0.0 * units.deg)\n    rod_journal rj1(angle: 180.0 * units.deg)\n    rod_journal rj2(angle: 0.0 * units.deg)\n    rod_journal rj3(angle: 180.0 * units.deg)\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n\n    piston_parameters piston_params(\n        mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: rod_mass,\n        moment_of_inertia: rod_moment_of_inertia(\n            mass: rod_mass,\n            length: rod_length\n        ),\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 1.325 * units.L,\n        plenum_cross_section_area: 20.0 * units.cm2,\n        intake_flow_rate: k_carb(800.0),\n        runner_flow_rate: k_carb(250.0),\n        runner_length: 12.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.9985,\n        velocity_decay: 0.5\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(1000.0),\n        primary_tube_length: 10.0 * units.inch,\n        primary_flow_rate: k_carb(200.0),\n        velocity_decay: 1.0,\n        volume: 100.0 * units.L\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 0.5 * 8,\n        impulse_response: ir_lib.mild_exhaust_0\n    )\n    exhaust_system exhaust1(\n        es_params,\n        audio_volume: 1.0 * 8,\n        impulse_response: ir_lib.mild_exhaust_0\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    cylinder_bank b0(bank_params, angle: 90.0 * units.deg)\n    cylinder_bank b1(bank_params, angle: -90.0 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.001)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.002)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3\n        )\n        .set_cylinder_head(\n            ej25_head(\n                flip_display: true,\n                intake_camshaft: camshaft.intake_cam_0,\n                exhaust_camshaft: camshaft.exhaust_cam_0)\n        )\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.001)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.002)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4\n        )\n        .set_cylinder_head(\n            ej25_head(\n                intake_camshaft: camshaft.intake_cam_1,\n                exhaust_camshaft: camshaft.exhaust_cam_1)\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 232 * units.deg,\n        gamma: 2.0,\n        lift: 9.78 * units.mm,\n        steps: 100\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 236 * units.deg,\n        gamma: 2.0,\n        lift: 9.60 * units.mm,\n        steps: 100\n    )\n\n    ej25_camshaft camshaft(\n        lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 117 * units.deg,\n        exhaust_lobe_center: 112 * units.deg,\n        base_radius: (34.0 / 2) * units.mm\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 25 * units.deg)\n        .add_sample(1000 * units.rpm, 25 * units.deg)\n        .add_sample(2000 * units.rpm, 30 * units.deg)\n        .add_sample(3000 * units.rpm, 40 * units.deg)\n        .add_sample(4000 * units.rpm, 40 * units.deg)\n\n    ignition_module ignition_module(\n        timing_curve: timing_curve,\n        rev_limit: 6500 * units.rpm,\n        limiter_duration: 0.08)\n    ignition_module\n            .connect_wire(wires.wire1, (0.0 / 4.0) * cycle)\n            .connect_wire(wires.wire3, (1.0 / 4.0) * cycle)\n            .connect_wire(wires.wire2, (2.0 / 4.0) * cycle)\n            .connect_wire(wires.wire4, (3.0 / 4.0) * cycle)\n\n    engine.add_ignition_module(ignition_module)\n}\n\nprivate node impreza {\n    alias output __out:\n        vehicle(\n            mass: 2700 * units.lb,\n            drag_coefficient: 0.2,\n            cross_sectional_area: (66 * units.inch) * (56 * units.inch),\n            diff_ratio: 3.9,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 300 * units.N\n        );\n}\n\nprivate node impreza_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 300 * units.lb_ft\n        )\n        .add_gear(3.636)\n        .add_gear(2.375)\n        .add_gear(1.761)\n        .add_gear(1.346)\n        .add_gear(0.971)\n        .add_gear(0.756);\n}\n\npublic node main {\n    set_engine(subaru_ej25())\n    set_vehicle(impreza())\n    set_transmission(impreza_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-1/07_audi_i5.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n}\n\nlabel cycle(2 * 360 * units.deg)\npublic node wb_ignition {\n    input wires;\n    input timing_curve;\n    input rev_limit: 7500 * units.rpm;\n    alias output __out:\n        ignition_module(\n            timing_curve: timing_curve,\n            rev_limit: rev_limit,\n            limiter_duration: 0.1\n        )\n            .connect_wire(wires.wire1, (0.0 / 5.0) * cycle)\n            .connect_wire(wires.wire2, (1.0 / 5.0) * cycle)\n            .connect_wire(wires.wire4, (2.0 / 5.0) * cycle)\n            .connect_wire(wires.wire5, (3.0 / 5.0) * cycle)\n            .connect_wire(wires.wire3, (4.0 / 5.0) * cycle);\n}\n\npublic node i5_camshaft_builder {\n    input lobe_profile: comp_cams_magnum_11_450_8_lobe_profile();\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 110.0 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;\n    input advance: 0.0 * units.deg;\n    input base_radius: 0.75 * units.inch;\n\n    output intake_cam: _intake_cam;\n    output exhaust_cam: _exhaust_cam;\n\n    camshaft_parameters params(\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot(2 * (360 / 5.0) * units.deg)\n    label rot360(360 * units.deg)\n\n    _exhaust_cam\n        .add_lobe(rot360 - exhaust_lobe_center)\n        .add_lobe((rot360 - exhaust_lobe_center) + 1 * rot)\n        .add_lobe((rot360 - exhaust_lobe_center) + 4 * rot)\n        .add_lobe((rot360 - exhaust_lobe_center) + 2 * rot)\n        .add_lobe((rot360 - exhaust_lobe_center) + 3 * rot)\n\n    _intake_cam\n        .add_lobe(rot360 + intake_lobe_center)\n        .add_lobe(rot360 + intake_lobe_center + 1 * rot)\n        .add_lobe(rot360 + intake_lobe_center + 4 * rot)\n        .add_lobe(rot360 + intake_lobe_center + 2 * rot)\n        .add_lobe(rot360 + intake_lobe_center + 3 * rot)\n}\n\nprivate node audi_i5_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 50 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 1.9 * units.inch * 1.9 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 1.25 * units.inch * 1.25 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 58 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 103 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 156 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 214 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 249 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 268 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 281 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 37 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 72 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 113 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 196 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 222 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 245 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 246 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: standard_valvetrain(\n            intake_camshaft: intake_camshaft,\n            exhaust_camshaft: exhaust_camshaft\n        ),\n        flip_display: flip_display\n    )\n}\n\npublic node audi_i5_2_2L {\n    alias output __out: engine;\n\n    wires wires()\n\n    engine engine(\n        name: \"Audi 2.3 inline 5\",\n        starter_torque: 200 * units.lb_ft,\n        redline: 6000 * units.rpm,\n        fuel: fuel(\n            max_turbulence_effect: 2.5,\n            max_burning_efficiency: 0.75),\n        hf_gain: 0.01,\n        noise: 1.0,\n        jitter: 0.299,\n        simulation_frequency: 17000\n    )\n\n    label stroke(79.5 * units.mm)\n    label bore(86.4 * units.mm)\n    label rod_length(5.142 * units.inch)\n    label rod_mass(535 * units.g)\n    label compression_height(32.8 * units.mm)\n    label crank_mass(9.39 * units.kg)\n    label flywheel_mass(6.8 * units.kg)\n    label flywheel_radius(6 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 20 * units.kg, radius: 8.0 * units.cm)\n    )\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 5.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: constants.pi / 2\n    )\n\n    rod_journal rj0(angle: (0.0 / 5.0) * 360 * units.deg)\n\trod_journal rj1(angle: (2.0 / 5.0) * 360 * units.deg)\n\trod_journal rj2(angle: (3.0 / 5.0) * 360 * units.deg)\n\trod_journal rj3(angle: (4.0 / 5.0) * 360 * units.deg)\n    rod_journal rj4(angle: (1.0 / 5.0) * 360 * units.deg)\n\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n        .add_rod_journal(rj4)\n\n    piston_parameters piston_params(\n        mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight\n        blowby: 0,\n        compression_height: compression_height,\n        wrist_pin_position: 0 * units.mm,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: rod_mass,\n        moment_of_inertia: rod_moment_of_inertia(\n            mass: rod_mass,\n            length: rod_length\n        ),\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    intake intake(\n        plenum_volume: 1.0 * units.L,\n        plenum_cross_section_area: 10.0 * units.cm2,\n        intake_flow_rate: k_carb(350.0),\n        runner_flow_rate: k_carb(175.0),\n        runner_length: 5.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.993\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(500.0),\n        primary_tube_length: 10.0 * units.inch,\n        primary_flow_rate: k_carb(100.0),\n        velocity_decay: 1.0,\n        volume: 50.0 * units.L\n    )\n\n    exhaust_system exhaust0(\n        es_params, audio_volume: 1.0, impulse_response: ir_lib.mild_exhaust_0)\n    exhaust_system exhaust1(\n        es_params, audio_volume: 0.8, impulse_response: ir_lib.mild_exhaust_0)\n\n    cylinder_bank b0(bank_params, angle: 0)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1,\n            sound_attenuation: 0.9\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.6)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2,\n            sound_attenuation: 0.8\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.6)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3,\n            sound_attenuation: 0.9\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.4)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4,\n            sound_attenuation: 1.0\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.4)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj4,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire5,\n            sound_attenuation: 1.1\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 210 * units.deg,\n        gamma: 2.0,\n        lift: 9.78 * units.mm,\n        steps: 100\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 215 * units.deg,\n        gamma: 2.0,\n        lift: 9.60 * units.mm,\n        steps: 100\n    )\n\n    i5_camshaft_builder camshaft(\n        lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 116 * units.deg,\n        exhaust_lobe_center: 116 * units.deg,\n        base_radius: (34.0 / 2) * units.mm\n    )\n\n    b0.set_cylinder_head (\n        audi_i5_head(\n            chamber_volume: 50 * units.cc,\n            intake_camshaft: camshaft.intake_cam,\n            exhaust_camshaft: camshaft.exhaust_cam,\n            flow_attenuation: 0.9\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(1000 * units.rpm, 12 * units.deg)\n        .add_sample(2000 * units.rpm, 20 * units.deg)\n        .add_sample(3000 * units.rpm, 26 * units.deg)\n        .add_sample(4000 * units.rpm, 30 * units.deg)\n        .add_sample(5000 * units.rpm, 34 * units.deg)\n        .add_sample(6000 * units.rpm, 38 * units.deg)\n        .add_sample(7000 * units.rpm, 38 * units.deg)\n\n    engine.add_ignition_module(\n        wb_ignition(\n            wires: wires,\n            timing_curve: timing_curve,\n            rev_limit: 6500 * units.rpm\n        )\n    )\n}\n\nprivate node audi_vehicle {\n    alias output __out:\n        vehicle(\n            mass: 2844 * units.lb,\n            drag_coefficient: 0.3,\n            cross_sectional_area: (66 * units.inch) * (50 * units.inch),\n            diff_ratio: 3.55,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 500 * units.N\n        );\n}\n\nprivate node audi_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 200 * units.lb_ft\n        )\n        .add_gear(3.417)\n        .add_gear(2.105)\n        .add_gear(1.429)\n        .add_gear(1.088)\n        .add_gear(0.970)\n        .add_gear(0.912);\n}\n\npublic node main {\n    set_engine(audi_i5_2_2L())\n    set_vehicle(audi_vehicle())\n    set_transmission(audi_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-1/08_radial_5.mr",
    "content": "import \"engine_sim.mr\"\n\nimport \"radial.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n}\n\nlabel cycle(2 * 360 * units.deg)\npublic node radial_5_distributor {\n    input wires;\n    input timing_curve;\n    input rev_limit: 5500 * units.rpm;\n    alias output __out:\n        ignition_module(\n            timing_curve: timing_curve,\n            rev_limit: rev_limit,\n            limiter_duration: 0.2\n        )\n            .connect_wire(wires.wire1, (0 / 5.0) * cycle)\n            .connect_wire(wires.wire3, (1 / 5.0) * cycle)\n            .connect_wire(wires.wire5, (2 / 5.0) * cycle)\n            .connect_wire(wires.wire2, (3 / 5.0) * cycle)\n            .connect_wire(wires.wire4, (4 / 5.0) * cycle);\n}\n\npublic node radial_5 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Radial 5\",\n        starter_torque: 150 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 3000 * units.rpm,\n        throttle_gamma: 2.0,\n        hf_gain: 0.00121,\n        noise: 0.623,\n        jitter: 0.042,\n        simulation_frequency: 12000\n    )\n\n    wires wires()\n\n    label slave_throw(2.9 * units.inch)\n    label stroke(5.5 * units.inch)\n    label bore(5 * units.inch)\n    label rod_length(12 * units.inch)\n    label compression_height(1.0 * units.inch)\n    label rod_mass(535 * units.g)\n    label crank_mass(20.39 * units.kg)\n    label flywheel_mass(100 * units.kg)\n    label flywheel_radius(5 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 10 * units.kg, radius: 2.0 * units.cm)\n    )\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: 10 * units.lb,\n        mass: 10 * units.lb,\n        friction_torque: 10.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: (90 - 0.5 * 45) * units.deg\n    )\n\n    rod_journal rj0(angle: 0.0)\n    c0\n        .add_rod_journal(rj0)\n\n    piston_parameters piston_params(\n        mass: 200 * units.g,\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: 100.0 * units.g,\n        moment_of_inertia: 0.0015884918028487504,\n        center_of_mass: 0.0,\n        length: rod_length - slave_throw\n    )\n\n    intake intake(\n        plenum_volume: 10.5 * units.L,\n        plenum_cross_section_area: 10.0 * units.cm2,\n        intake_flow_rate: k_carb(1000.0),\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.995,\n        velocity_decay: 1.0\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(1000.0),\n        primary_tube_length: 70.0 * units.inch,\n        primary_flow_rate: k_carb(300.0),\n        velocity_decay: 0.75,\n        volume: 10.0 * units.L\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 1.0,\n        impulse_response: ir_lib.minimal_muffling_01\n    )\n\n    exhaust_system exhaust1(\n        es_params,\n        audio_volume: 0.2,\n        impulse_response: ir_lib.minimal_muffling_01\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    connecting_rod master(\n        connecting_rod_parameters(\n            cr_params,\n            slave_throw: slave_throw,\n            length: rod_length\n        )\n    )\n\n    rod_journal sj0(angle: (0 / 5.0) * 360 * units.deg)\n    rod_journal sj1(angle: (1 / 5.0) * 360 * units.deg)\n    rod_journal sj2(angle: (2 / 5.0) * 360 * units.deg)\n    rod_journal sj3(angle: (3 / 5.0) * 360 * units.deg)\n    rod_journal sj4(angle: (4 / 5.0) * 360 * units.deg)\n    master\n        .add_slave_journal(sj0)\n        .add_slave_journal(sj1)\n        .add_slave_journal(sj2)\n        .add_slave_journal(sj3)\n        .add_slave_journal(sj4)\n\n    cylinder_bank b0(bank_params, angle: (0 / 5.0) * 360 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: master,\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1\n        )\n\n    cylinder_bank b1(bank_params, angle: (1 / 5.0) * 360 * units.deg)\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.03)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: sj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire5\n        )\n\n    cylinder_bank b2(bank_params, angle: (2 / 5.0) * 360 * units.deg)\n    b2\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: sj2,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4\n        )\n\n    cylinder_bank b3(bank_params, angle: (3 / 5.0) * 360 * units.deg)\n    b3\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: sj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire3\n        )\n    cylinder_bank b4(bank_params, angle: (4 / 5.0) * 360 * units.deg)\n    b4\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.5)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: sj4,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n        .add_cylinder_bank(b2)\n        .add_cylinder_bank(b3)\n        .add_cylinder_bank(b4)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe lobe(\n        duration_at_50_thou: 260 * units.deg,\n        gamma: 0.9,\n        lift: 800 * units.thou,\n        steps: 100\n    )\n\n    b0.set_cylinder_head (\n        radial_head(\n            offset: 0 / 5.0,\n            lobe_profile: lobe\n        )\n    )\n\n    b1.set_cylinder_head (\n        radial_head(\n            offset: 2 / 5.0,\n            lobe_profile: lobe\n        )\n    )\n\n    b2.set_cylinder_head (\n        radial_head(\n            offset: 4 / 5.0,\n            lobe_profile: lobe\n        )\n    )\n\n    b3.set_cylinder_head (\n        radial_head(\n            offset: 1 / 5.0,\n            lobe_profile: lobe\n        )\n    )\n\n    b4.set_cylinder_head (\n        radial_head(\n            offset: 3 / 5.0,\n            lobe_profile: lobe\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 18 * units.deg)\n        .add_sample(1000 * units.rpm, 18 * units.deg)\n        .add_sample(2000 * units.rpm, 30 * units.deg)\n        .add_sample(3000 * units.rpm, 40 * units.deg)\n        .add_sample(4000 * units.rpm, 40 * units.deg)\n\n    engine.add_ignition_module(\n        radial_5_distributor(\n            wires: wires,\n            timing_curve: timing_curve,\n            rev_limit: 3500 * units.rpm\n        ))\n}\n\nprivate node propellor {\n    alias output __out:\n        vehicle(\n            mass: 100 * units.lb,\n            drag_coefficient: 0.5,\n            cross_sectional_area: (15 * units.inch) * (47 * units.inch),\n            diff_ratio: 1.0,\n            tire_radius: 1.0,\n            rolling_resistance: 300 * units.N\n        );\n}\n\nprivate node direct_drive {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 500 * units.lb_ft\n        )\n        .add_gear(1.0);\n}\n\npublic node main {\n    set_engine(radial_5())\n    set_vehicle(propellor())\n    set_transmission(direct_drive())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-1/README.md",
    "content": "## Engines from *Simulating VTEC, a radial-5 engine and more with my engine simulator (there is sound)*\n\nThis folder contains all the engines from [*Simulating VTEC, a radial-5 engine and more with my engine simulator (there is sound)*](https://youtu.be/WPCMNnM6D0k). Each file contains a node called `main` and it can be used in `main.mr` as follows.\n\n```\nimport \"engines/atg-video-1/<insert filename>\"\n\nmain()\n```\n\nThe following engines are included:\n1. Honda 1-cylinder ATV engine\n2. Kohler Command Pro CH750\n3. Harley Davidson Shovelhead\n4. Hayabusa Inline-4\n5. Honda B18C5\n6. Subaru EJ25 (version 1)\n7. Audi Inline-5\n8. Radial-5\n"
  },
  {
    "path": "assets/engines/atg-video-1/radial.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nlabel cycle(2 * 360 * units.deg)\n\npublic node radial_head {\n    input offset;\n    input lobe_profile;\n    input chamber_volume: 290 * units.cc;\n    alias output __head:\n        generic_small_engine_head(\n            chamber_volume: chamber_volume,\n            intake_camshaft: camshaft.intake_cam,\n            exhaust_camshaft: camshaft.exhaust_cam,\n            flow_attenuation: 2.0,\n            intake_runner_cross_section_area: 20.0 * units.cm2,\n            exhaust_runner_cross_section_area: 20.0 * units.cm2\n        );\n\n    radial_camshaft camshaft(\n        lobe_profile: lobe_profile,\n        offset: offset\n    )\n}\n\npublic node radial_camshaft {\n    input lobe_profile;\n    input offset;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;  \n    input advance: 0 * units.deg;\n    input base_radius: 1.0 * units.inch;\n\n    output intake_cam: _intake_cam;\n    output exhaust_cam: _exhaust_cam;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot180(180 * units.deg)\n    label rot360(360 * units.deg)\n\n    _exhaust_cam\n        .add_lobe(rot360 - exhaust_lobe_center + offset * cycle)\n    _intake_cam\n        .add_lobe(rot360 + intake_lobe_center + offset * cycle)\n}\n"
  },
  {
    "path": "assets/engines/atg-video-2/01_subaru_ej25_eh.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\nlabel cycle(2 * 360 * units.deg)\n\nprivate node turbulence_to_flame_speed_ratio {\n    alias output __out:\n        function(5.0)\n            .add_sample(0.0, 1.0 * 3.0)\n            .add_sample(5.0, 1.0 * 1.5 * 5.0)\n            .add_sample(10.0, 1.0 * 1.5 * 10.0)\n            .add_sample(15.0, 1.1 * 1.5 * 15.0)\n            .add_sample(20.0, 1.25 * 1.5 * 20.0)\n            .add_sample(25.0, 1.25 * 1.5 * 25.0)\n            .add_sample(30.0, 1.25 * 1.5 * 30.0)\n            .add_sample(35.0, 1.25 * 1.5 * 35.0)\n            .add_sample(40.0, 1.25 * 1.5 * 40.0)\n            .add_sample(45.0, 1.25 * 1.5 * 45.0);\n}\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n}\n\nprivate node ej25_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 67 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 1.25 * units.inch * 1.25 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 58 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 103 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 156 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 214 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 249 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 268 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 281 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 37 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 72 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 113 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 196 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 222 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 245 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 246 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: standard_valvetrain(\n            intake_camshaft: intake_camshaft,\n            exhaust_camshaft: exhaust_camshaft\n        ),\n        flip_display: flip_display\n    )\n}\n\nprivate node ej25_camshaft {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;  \n    input advance: 0 * units.deg; \n    input base_radius: 1.0 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output exhaust_cam_0: _exhaust_cam_0;\n\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot180(180 * units.deg)\n    label rot360(360 * units.deg)\n\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center + (0.0 / 4) * cycle)\n        .add_lobe(rot360 - exhaust_lobe_center + (1.0 / 4) * cycle)\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center + (0.0 / 4) * cycle)\n        .add_lobe(rot360 + intake_lobe_center + (1.0 / 4) * cycle)\n\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + (2.0 / 4) * cycle)\n        .add_lobe(rot360 - exhaust_lobe_center + (3.0 / 4) * cycle)\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + (2.0 / 4) * cycle)\n        .add_lobe(rot360 + intake_lobe_center + (3.0 / 4) * cycle)\n}\n\npublic node subaru_ej25 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Subaru EJ25\",\n        starter_torque: 70 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 6500 * units.rpm,\n        fuel: fuel(\n            max_burning_efficiency: 0.9,\n            turbulence_to_flame_speed_ratio: turbulence_to_flame_speed_ratio()\n        ),\n        throttle_gamma: 2.0,\n        hf_gain: 0.01,\n        noise: 1.0,\n        jitter: 0.5,\n        simulation_frequency: 20000\n    )\n\n    wires wires()\n\n    label stroke(79 * units.mm)\n    label bore(99.5 * units.mm)\n    label rod_length(5.142 * units.inch)\n    label rod_mass(535 * units.g)\n    label compression_height(1.0 * units.inch)\n    label crank_mass(9.39 * units.kg)\n    label flywheel_mass(6.8 * units.kg)\n    label flywheel_radius(6 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) * 2\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 10 * units.kg, radius: 6.0 * units.cm)\n    )\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 1.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: 180 * units.deg\n    )\n\n    rod_journal rj0(angle: 0.0 * units.deg)\n    rod_journal rj1(angle: 180.0 * units.deg)\n    rod_journal rj2(angle: 0.0 * units.deg)\n    rod_journal rj3(angle: 180.0 * units.deg)\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n\n    piston_parameters piston_params(\n        mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: rod_mass,\n        moment_of_inertia: rod_moment_of_inertia(\n            mass: rod_mass,\n            length: rod_length\n        ),\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 1.325 * units.L,\n        plenum_cross_section_area: 20.0 * units.cm2,\n        intake_flow_rate: k_carb(400.0),\n        runner_flow_rate: k_carb(100.0),\n        runner_length: 12.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.9978,\n        velocity_decay: 1.0\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(1000.0),\n        primary_tube_length: 40.0 * units.inch,\n        primary_flow_rate: k_carb(400.0),\n        velocity_decay: 1.0\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        length: 500 * units.mm,\n        audio_volume: 0.5 * 0.02,\n        impulse_response: ir_lib.minimal_muffling_02\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    cylinder_bank b0(bank_params, angle: 90.0 * units.deg)\n    cylinder_bank b1(bank_params, angle: -90.0 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.001)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1,\n            primary_length: 2.0 * units.inch,\n            sound_attenuation: 0.9\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.002)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3,\n            primary_length: 3.0 * units.inch,\n            sound_attenuation: 1.0\n        )\n        .set_cylinder_head(\n            ej25_head(\n                flip_display: true,\n                intake_camshaft: camshaft.intake_cam_0,\n                exhaust_camshaft: camshaft.exhaust_cam_0)\n        )\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.001)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire2,\n            primary_length: 3.0 * units.inch,\n            sound_attenuation: 1.1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.002)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire4,\n            primary_length: 5.0 * units.inch,\n            sound_attenuation: 0.9\n        )\n        .set_cylinder_head(\n            ej25_head(\n                flip_display: false,\n                intake_camshaft: camshaft.intake_cam_1,\n                exhaust_camshaft: camshaft.exhaust_cam_1)\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 232 * units.deg,\n        gamma: 2.0,\n        lift: 9.78 * units.mm,\n        steps: 100\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 236 * units.deg,\n        gamma: 2.0,\n        lift: 9.60 * units.mm,\n        steps: 100\n    )\n\n    ej25_camshaft camshaft(\n        lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 117 * units.deg,\n        exhaust_lobe_center: 112 * units.deg,\n        base_radius: (34.0 / 2) * units.mm\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 25 * units.deg)\n        .add_sample(1000 * units.rpm, 25 * units.deg)\n        .add_sample(2000 * units.rpm, 30 * units.deg)\n        .add_sample(3000 * units.rpm, 40 * units.deg)\n        .add_sample(4000 * units.rpm, 40 * units.deg)\n\n    ignition_module ignition_module(\n        timing_curve: timing_curve,\n        rev_limit: 6800 * units.rpm,\n        limiter_duration: 0.16)\n    ignition_module\n            .connect_wire(wires.wire1, (0.0 / 4.0) * cycle)\n            .connect_wire(wires.wire3, (1.0 / 4.0) * cycle)\n            .connect_wire(wires.wire2, (2.0 / 4.0) * cycle)\n            .connect_wire(wires.wire4, (3.0 / 4.0) * cycle)\n\n    engine.add_ignition_module(ignition_module)\n}\n\nlabel car_mass(2700 * units.lb)\n\nprivate node impreza {\n    alias output __out:\n        vehicle(\n            mass: 2700 * units.lb,\n            drag_coefficient: 0.3,\n            cross_sectional_area: (72 * units.inch) * (56 * units.inch),\n            diff_ratio: 3.9,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 0.015 * car_mass * 9.81 \n        );\n}\n\nprivate node impreza_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 300 * units.lb_ft\n        )\n        .add_gear(3.636)\n        .add_gear(2.375)\n        .add_gear(1.761)\n        .add_gear(1.346)\n        .add_gear(0.971)\n        .add_gear(0.756);\n}\n\npublic node main {\n    set_engine(subaru_ej25())\n    set_vehicle(impreza())\n    set_transmission(impreza_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-2/02_subaru_ej25_uh.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\nlabel cycle(2 * 360 * units.deg)\n\nprivate node turbulence_to_flame_speed_ratio {\n    alias output __out:\n        function(5.0)\n            .add_sample(0.0, 1.0 * 3.0)\n            .add_sample(5.0, 1.0 * 1.5 * 5.0)\n            .add_sample(10.0, 1.0 * 1.5 * 10.0)\n            .add_sample(15.0, 1.1 * 1.5 * 15.0)\n            .add_sample(20.0, 1.25 * 1.5 * 20.0)\n            .add_sample(25.0, 1.25 * 1.5 * 25.0)\n            .add_sample(30.0, 1.25 * 1.5 * 30.0)\n            .add_sample(35.0, 1.25 * 1.5 * 35.0)\n            .add_sample(40.0, 1.25 * 1.5 * 40.0)\n            .add_sample(45.0, 1.25 * 1.5 * 45.0);\n}\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n}\n\nprivate node ej25_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 67 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 1.25 * units.inch * 1.25 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 58 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 103 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 156 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 214 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 249 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 268 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 281 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 37 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 72 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 113 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 196 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 222 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 245 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 246 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: standard_valvetrain(\n            intake_camshaft: intake_camshaft,\n            exhaust_camshaft: exhaust_camshaft\n        ),\n        flip_display: flip_display\n    )\n}\n\nprivate node ej25_camshaft {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;  \n    input advance: 0 * units.deg; \n    input base_radius: 1.0 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output exhaust_cam_0: _exhaust_cam_0;\n\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot180(180 * units.deg)\n    label rot360(360 * units.deg)\n\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center + (0.0 / 4) * cycle)\n        .add_lobe(rot360 - exhaust_lobe_center + (1.0 / 4) * cycle)\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center + (0.0 / 4) * cycle)\n        .add_lobe(rot360 + intake_lobe_center + (1.0 / 4) * cycle)\n\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + (2.0 / 4) * cycle)\n        .add_lobe(rot360 - exhaust_lobe_center + (3.0 / 4) * cycle)\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + (2.0 / 4) * cycle)\n        .add_lobe(rot360 + intake_lobe_center + (3.0 / 4) * cycle)\n}\n\npublic node subaru_ej25 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Subaru EJ25\",\n        starter_torque: 70 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 6500 * units.rpm,\n        fuel: fuel(\n            max_burning_efficiency: 0.9,\n            turbulence_to_flame_speed_ratio: turbulence_to_flame_speed_ratio()\n        ),\n        throttle_gamma: 2.0,\n        hf_gain: 0.01,\n        noise: 1.0,\n        jitter: 0.5,\n        simulation_frequency: 20000\n    )\n\n    wires wires()\n\n    label stroke(79 * units.mm)\n    label bore(99.5 * units.mm)\n    label rod_length(5.142 * units.inch)\n    label rod_mass(535 * units.g)\n    label compression_height(1.0 * units.inch)\n    label crank_mass(9.39 * units.kg)\n    label flywheel_mass(6.8 * units.kg)\n    label flywheel_radius(6 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) * 2\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 10 * units.kg, radius: 6.0 * units.cm)\n    )\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 1.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: 180 * units.deg\n    )\n\n    rod_journal rj0(angle: 0.0 * units.deg)\n    rod_journal rj1(angle: 180.0 * units.deg)\n    rod_journal rj2(angle: 0.0 * units.deg)\n    rod_journal rj3(angle: 180.0 * units.deg)\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n\n    piston_parameters piston_params(\n        mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: rod_mass,\n        moment_of_inertia: rod_moment_of_inertia(\n            mass: rod_mass,\n            length: rod_length\n        ),\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 1.325 * units.L,\n        plenum_cross_section_area: 20.0 * units.cm2,\n        intake_flow_rate: k_carb(400.0),\n        runner_flow_rate: k_carb(100.0),\n        runner_length: 12.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.9978,\n        velocity_decay: 1.0\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(1000.0),\n        primary_tube_length: 24.0 * units.inch,\n        primary_flow_rate: k_carb(400.0),\n        velocity_decay: 1.0\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        length: 500 * units.mm,\n        audio_volume: 0.5 * 0.02,\n        impulse_response: ir_lib.minimal_muffling_02\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    cylinder_bank b0(bank_params, angle: 90.0 * units.deg)\n    cylinder_bank b1(bank_params, angle: -90.0 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.001)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1,\n            primary_length: 2.0 * units.inch,\n            sound_attenuation: 0.9\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.002)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3,\n            primary_length: 3.0 * units.inch,\n            sound_attenuation: 1.0\n        )\n        .set_cylinder_head(\n            ej25_head(\n                flip_display: true,\n                intake_camshaft: camshaft.intake_cam_0,\n                exhaust_camshaft: camshaft.exhaust_cam_0)\n        )\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.001)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire2,\n            primary_length: 500 * units.mm,\n            sound_attenuation: 1.1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.002)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire4,\n            primary_length: 550 * units.mm,\n            sound_attenuation: 0.9\n        )\n        .set_cylinder_head(\n            ej25_head(\n                flip_display: false,\n                intake_camshaft: camshaft.intake_cam_1,\n                exhaust_camshaft: camshaft.exhaust_cam_1)\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 232 * units.deg,\n        gamma: 2.0,\n        lift: 9.78 * units.mm,\n        steps: 100\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 236 * units.deg,\n        gamma: 2.0,\n        lift: 9.60 * units.mm,\n        steps: 100\n    )\n\n    ej25_camshaft camshaft(\n        lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 117 * units.deg,\n        exhaust_lobe_center: 112 * units.deg,\n        base_radius: (34.0 / 2) * units.mm\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 25 * units.deg)\n        .add_sample(1000 * units.rpm, 25 * units.deg)\n        .add_sample(2000 * units.rpm, 30 * units.deg)\n        .add_sample(3000 * units.rpm, 40 * units.deg)\n        .add_sample(4000 * units.rpm, 40 * units.deg)\n\n    ignition_module ignition_module(\n        timing_curve: timing_curve,\n        rev_limit: 6800 * units.rpm,\n        limiter_duration: 0.16)\n    ignition_module\n            .connect_wire(wires.wire1, (0.0 / 4.0) * cycle)\n            .connect_wire(wires.wire3, (1.0 / 4.0) * cycle)\n            .connect_wire(wires.wire2, (2.0 / 4.0) * cycle)\n            .connect_wire(wires.wire4, (3.0 / 4.0) * cycle)\n\n    engine.add_ignition_module(ignition_module)\n}\n\nlabel car_mass(2700 * units.lb)\n\nprivate node impreza {\n    alias output __out:\n        vehicle(\n            mass: car_mass,\n            drag_coefficient: 0.3,\n            cross_sectional_area: (72 * units.inch) * (56 * units.inch),\n            diff_ratio: 3.9,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 0.015 * car_mass * 9.81 \n        );\n}\n\nprivate node impreza_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 300 * units.lb_ft\n        )\n        .add_gear(3.636)\n        .add_gear(2.375)\n        .add_gear(1.761)\n        .add_gear(1.346)\n        .add_gear(0.971)\n        .add_gear(0.756);\n}\n\npublic node main {\n    set_engine(subaru_ej25())\n    set_vehicle(impreza())\n    set_transmission(impreza_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-2/03_2jz.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n    output wire6: ignition_wire();\n}\n\nlabel cycle(2 * 360 * units.deg)\npublic node wb_ignition {\n    input wires;\n    input timing_curve;\n    input rev_limit: 7500 * units.rpm;\n    alias output __out:\n        ignition_module(\n            timing_curve: timing_curve,\n            rev_limit: rev_limit,\n            limiter_duration: 0.1\n        )\n            .connect_wire(wires.wire1, (0.0 / 6.0) * cycle)\n            .connect_wire(wires.wire5, (1.0 / 6.0) * cycle)\n            .connect_wire(wires.wire3, (2.0 / 6.0) * cycle)\n            .connect_wire(wires.wire6, (3.0 / 6.0) * cycle)\n            .connect_wire(wires.wire2, (4.0 / 6.0) * cycle)\n            .connect_wire(wires.wire4, (5.0 / 6.0) * cycle);\n}\n\npublic node t2jz_camshaft_builder {\n    input lobe_profile: comp_cams_magnum_11_450_8_lobe_profile();\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 110.0 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;\n    input advance: 0.0 * units.deg;\n    input base_radius: 0.75 * units.inch;\n\n    output intake_cam: _intake_cam;\n    output exhaust_cam: _exhaust_cam;\n\n    camshaft_parameters params(\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot(2 * (360 / 6.0) * units.deg)\n    label rot360(360 * units.deg)\n\n    // 1 5 3 6 2 4\n    _exhaust_cam\n        .add_lobe((rot360 - exhaust_lobe_center) + 0 * rot)\n        .add_lobe((rot360 - exhaust_lobe_center) + 4 * rot)\n        .add_lobe((rot360 - exhaust_lobe_center) + 2 * rot)\n        .add_lobe((rot360 - exhaust_lobe_center) + 5 * rot)\n        .add_lobe((rot360 - exhaust_lobe_center) + 1 * rot)\n        .add_lobe((rot360 - exhaust_lobe_center) + 3 * rot)\n\n    _intake_cam\n        .add_lobe(rot360 + intake_lobe_center + 0 * rot)\n        .add_lobe(rot360 + intake_lobe_center + 4 * rot)\n        .add_lobe(rot360 + intake_lobe_center + 2 * rot)\n        .add_lobe(rot360 + intake_lobe_center + 5 * rot)\n        .add_lobe(rot360 + intake_lobe_center + 1 * rot)\n        .add_lobe(rot360 + intake_lobe_center + 3 * rot)\n}\n\nprivate node t2jz_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 50 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 1.9 * units.inch * 1.9 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 1.25 * units.inch * 1.25 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 58 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 103 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 156 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 214 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 249 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 268 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 281 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 37 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 72 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 113 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 196 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 222 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 245 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 246 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: standard_valvetrain(\n            intake_camshaft: intake_camshaft,\n            exhaust_camshaft: exhaust_camshaft\n        ),\n        flip_display: flip_display\n    )\n}\n\npublic node t2jz {\n    alias output __out: engine;\n\n    wires wires()\n\n    engine engine(\n        name: \"2JZ [I6]\",\n        starter_torque: 200 * units.lb_ft,\n        redline: 6000 * units.rpm,\n        fuel: fuel(\n            max_burning_efficiency: 1.0\n        ),\n        hf_gain: 0.01,\n        noise: 1.0,\n        jitter: 0.23,\n        simulation_frequency: 10000\n    )\n\n    label stroke(86.0 * units.mm)\n    label bore(86.0 * units.mm)\n    label rod_length(142 * units.mm)\n    label rod_mass(500 * units.g)\n    label compression_height(32.8 * units.mm)\n    label crank_mass(15 * units.kg)\n    label flywheel_mass(10 * units.kg)\n    label flywheel_radius(7 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 20 * units.kg, radius: 8.0 * units.cm)\n    )\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 5.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: constants.pi / 2\n    )\n\n    // 1 5 3 6 2 4\n    rod_journal rj0(angle: 0 * units.deg)\n\trod_journal rj1(angle: 480 * units.deg)\n\trod_journal rj2(angle: 240 * units.deg)\n\trod_journal rj3(angle: 600 * units.deg)\n    rod_journal rj4(angle: 120 * units.deg)\n    rod_journal rj5(angle: 360 * units.deg)\n\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n        .add_rod_journal(rj4)\n        .add_rod_journal(rj5)\n\n    piston_parameters piston_params(\n        mass: (200 + 50) * units.g,\n        blowby: 0,\n        compression_height: compression_height,\n        wrist_pin_position: 0 * units.mm,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: rod_mass,\n        moment_of_inertia: rod_moment_of_inertia(\n            mass: rod_mass,\n            length: rod_length\n        ),\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    intake intake(\n        plenum_volume: 1.0 * units.L,\n        plenum_cross_section_area: 10.0 * units.cm2,\n        intake_flow_rate: k_carb(500.0),\n        runner_flow_rate: k_carb(200.0),\n        runner_length: 40.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.9965\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(1000.0),\n        primary_tube_length: 40.0 * units.inch,\n        primary_flow_rate: k_carb(400.0),\n        velocity_decay: 1.0\n    )\n    \n    exhaust_system exhaust0(\n        es_params,\n        length: 100.0 * units.inch,\n        audio_volume: 0.2,\n        impulse_response: ir_lib.mild_exhaust_0_reverb)\n    exhaust_system exhaust1(\n        es_params,\n        length: 100.0 * units.inch,\n        audio_volume: 0.2,\n        impulse_response: ir_lib.mild_exhaust_0_reverb)\n\n    label spacing(0.5 * units.inch)\n\n    cylinder_bank b0(bank_params, angle: 0)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1,\n            primary_length: spacing * 5,\n            sound_attenuation: 0.9\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.05)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire2,\n            primary_length: spacing * 4,\n            sound_attenuation: 0.95\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3,\n            primary_length: spacing * 3,\n            sound_attenuation: 0.9\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.05)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4,\n            primary_length: spacing * 3,\n            sound_attenuation: 0.97\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj4,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire5,\n            primary_length: spacing * 4,\n            sound_attenuation: 0.98\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.05)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj5,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire6,\n            primary_length: spacing * 5,\n            sound_attenuation: 0.93\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 220 * units.deg,\n        gamma: 1.1,\n        lift: 9.78 * units.mm,\n        steps: 100\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 220 * units.deg,\n        gamma: 1.1,\n        lift: 9.60 * units.mm,\n        steps: 100\n    )\n\n    t2jz_camshaft_builder camshaft(\n        lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 116 * units.deg,\n        exhaust_lobe_center: 116 * units.deg,\n        base_radius: (34.0 / 2) * units.mm\n    )\n\n    b0.set_cylinder_head (\n        t2jz_head(\n            chamber_volume: 50 * units.cc,\n            intake_camshaft: camshaft.intake_cam,\n            exhaust_camshaft: camshaft.exhaust_cam,\n            flow_attenuation: 0.9\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(1000 * units.rpm, 12 * units.deg)\n        .add_sample(2000 * units.rpm, 20 * units.deg)\n        .add_sample(3000 * units.rpm, 26 * units.deg)\n        .add_sample(4000 * units.rpm, 30 * units.deg)\n        .add_sample(5000 * units.rpm, 34 * units.deg)\n        .add_sample(6000 * units.rpm, 38 * units.deg)\n        .add_sample(7000 * units.rpm, 38 * units.deg)\n\n    engine.add_ignition_module(\n        wb_ignition(\n            wires: wires,\n            timing_curve: timing_curve,\n            rev_limit: 6500 * units.rpm\n        )\n    )\n}\n\nprivate node supra_vehicle {\n    alias output __out:\n        vehicle(\n            mass: 3400 * units.lb,\n            drag_coefficient: 0.4,\n            cross_sectional_area: (66 * units.inch) * (50 * units.inch),\n            diff_ratio: 3.15,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 500 * units.N\n        );\n}\n\nprivate node supra_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 500 * units.lb_ft\n        )\n        .add_gear(5.25)\n        .add_gear(3.36)\n        .add_gear(2.17)\n        .add_gear(1.72)\n        .add_gear(1.32)\n        .add_gear(1.0);\n}\n\npublic node main {\n    set_engine(t2jz())\n    set_vehicle(supra_vehicle())\n    set_transmission(supra_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-2/04_60_degree_v6.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\nlabel cycle(2 * 360 * units.deg)\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n    output wire6: ignition_wire();\n}\n\nprivate node v6_60_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 67 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 1.35 * units.inch * 1.35 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 2.0 * units.inch * 2.0 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 58 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 103 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 156 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 214 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 249 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 268 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 281 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 37 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 72 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 113 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 196 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 222 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 245 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 246 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: standard_valvetrain(\n            intake_camshaft: intake_camshaft,\n            exhaust_camshaft: exhaust_camshaft\n        ),\n        flip_display: flip_display\n    )\n}\n\nprivate node v6_60_camshaft {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;  \n    input advance: 0 * units.deg; \n    input base_radius: 0.5 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output exhaust_cam_0: _exhaust_cam_0;\n\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot180(180 * units.deg)\n    label rot360(360 * units.deg)\n\n    // 1 2 3 4 5 6\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center + 0 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 240 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 480 * units.deg)\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center + 0 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 240 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 480 * units.deg)\n\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + 120 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 360 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 600 * units.deg)\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + 120 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 360 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 600 * units.deg)\n}\n\npublic node v6_60 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Generic 60 deg. V6\",\n        starter_torque: 70 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 5500 * units.rpm,\n        throttle_gamma: 2.0\n    )\n\n    wires wires()\n\n    label stroke(3.48 * units.inch)\n    label bore(3.5 * units.inch)\n    label rod_length(5.142 * units.inch)\n    label rod_mass(535 * units.g)\n    label compression_height(1.0 * units.inch)\n    label crank_mass(50 * units.lb)\n    label flywheel_mass(30 * units.lb)\n    label flywheel_radius(7 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 10 * units.kg, radius: 1.0 * units.cm)\n    )\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 20.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: (90 + 30) * units.deg\n    )\n\n    rod_journal rj0(angle: 0.0 * units.deg)\n    rod_journal rj1(angle: (0 + 60) * units.deg)\n    rod_journal rj2(angle: 240.0 * units.deg)\n    rod_journal rj3(angle: (240.0 + 60) * units.deg)\n    rod_journal rj4(angle: 120.0 * units.deg)\n    rod_journal rj5(angle: (120.0 + 60) * units.deg)\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n        .add_rod_journal(rj4)\n        .add_rod_journal(rj5)\n\n    piston_parameters piston_params(\n        mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: rod_mass,\n        moment_of_inertia: rod_moment_of_inertia(\n            mass: rod_mass,\n            length: rod_length\n        ),\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 1.325 * units.L,\n        plenum_cross_section_area: 20.0 * units.cm2,\n        intake_flow_rate: k_carb(400.0),\n        runner_flow_rate: k_carb(250.0),\n        runner_length: 4.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.994,\n        velocity_decay: 0.5\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(1000.0),\n        primary_tube_length: 20.0 * units.inch,\n        primary_flow_rate: k_carb(500.0),\n        velocity_decay: 1.0,\n        length: 100.0 * units.inch\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 1.0,\n        impulse_response: ir_lib.mild_exhaust_0_reverb\n    )\n    exhaust_system exhaust1(\n        es_params,\n        audio_volume: 1.0,\n        impulse_response: ir_lib.mild_exhaust_0_reverb\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    label spacing(5.0 * units.inch)\n\n    cylinder_bank b0(bank_params, angle: 30.0 * units.deg)\n    cylinder_bank b1(bank_params, angle: -30.0 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1,\n            sound_attenuation: 0.9,\n            primary_length: spacing * 2\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3,\n            sound_attenuation: 0.95,\n            primary_length: spacing * 1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj4,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire5,\n            primary_length: spacing * 0\n        )\n        .set_cylinder_head(\n            v6_60_head(\n                flip_display: true,\n                intake_camshaft: camshaft.intake_cam_0,\n                exhaust_camshaft: camshaft.exhaust_cam_0)\n        )\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2,\n            sound_attenuation: 0.95,\n            primary_length: spacing * 2\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4,\n            sound_attenuation: 0.9,\n            primary_length: spacing * 1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj5,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire6,\n            sound_attenuation: 1.0,\n            primary_length: spacing * 0\n        )\n        .set_cylinder_head(\n            v6_60_head(\n                intake_camshaft: camshaft.intake_cam_1,\n                exhaust_camshaft: camshaft.exhaust_cam_1,\n                flip_display: false)\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 222 * units.deg,\n        gamma: 1.0,\n        lift: 400 * units.thou,\n        steps: 100\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 226 * units.deg,\n        gamma: 1.0,\n        lift: 300 * units.thou,\n        steps: 100\n    )\n\n    v6_60_camshaft camshaft(\n        lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 117 * units.deg,\n        exhaust_lobe_center: 112 * units.deg,\n        base_radius: 0.75 * units.inch\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(1000 * units.rpm, 20 * units.deg)\n        .add_sample(2000 * units.rpm, 25 * units.deg)\n        .add_sample(3000 * units.rpm, 30 * units.deg)\n        .add_sample(4000 * units.rpm, 30 * units.deg)\n\n    ignition_module ignition_module(\n        timing_curve: timing_curve,\n        rev_limit: 5600 * units.rpm,\n        limiter_duration: 0.2)\n    ignition_module\n            .connect_wire(wires.wire1, 0 * units.deg)\n            .connect_wire(wires.wire2, 120 * units.deg)\n            .connect_wire(wires.wire3, 240 * units.deg)\n            .connect_wire(wires.wire4, 360 * units.deg)\n            .connect_wire(wires.wire5, 480 * units.deg)\n            .connect_wire(wires.wire6, 600 * units.deg)\n\n    engine.add_ignition_module(ignition_module)\n}\n\nlabel car_mass(2700 * units.lb)\nprivate node random_car {\n    alias output __out:\n        vehicle(\n            mass: car_mass,\n            drag_coefficient: 0.3,\n            cross_sectional_area: (72 * units.inch) * (56 * units.inch),\n            diff_ratio: 3.9,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 0.015 * car_mass * 9.81\n        );\n}\n\nprivate node random_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 300 * units.lb_ft\n        )\n        .add_gear(3.636)\n        .add_gear(2.375)\n        .add_gear(1.761)\n        .add_gear(1.346)\n        .add_gear(0.971)\n        .add_gear(0.756);\n}\n\npublic node main {\n    set_engine(v6_60())\n    set_vehicle(random_car())\n    set_transmission(random_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-2/05_odd_fire_v6.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\nlabel cycle(2 * 360 * units.deg)\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n    output wire6: ignition_wire();\n}\n\nprivate node odd_fire_v6_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 67 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 1.35 * units.inch * 1.35 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 2.0 * units.inch * 2.0 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 58 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 103 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 156 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 214 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 249 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 268 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 281 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 37 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 72 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 113 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 196 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 222 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 245 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 246 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: standard_valvetrain(\n            intake_camshaft: intake_camshaft,\n            exhaust_camshaft: exhaust_camshaft\n        ),\n        flip_display: flip_display\n    )\n}\n\nprivate node odd_fire_v6_camshaft {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;  \n    input advance: 0 * units.deg; \n    input base_radius: 0.5 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output exhaust_cam_0: _exhaust_cam_0;\n\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot180(180 * units.deg)\n    label rot360(360 * units.deg)\n\n    // 1 6 5 4 3 2\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center + 0 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 480 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 240 * units.deg)\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center + 0 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 480 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 240 * units.deg)\n\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + 630 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 390 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 150 * units.deg)\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + 630 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 390 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 150 * units.deg)\n}\n\npublic node odd_fire_v6_90 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Generic Odd-fire V6 (Common Rod Jnl.)\",\n        starter_torque: 70 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 5500 * units.rpm,\n        fuel: fuel(\n            //max_turbulence_effect: 2.5,\n            //burning_efficiency_randomness: 0.5,\n            //max_burning_efficiency: 0.75\n        ),\n        throttle_gamma: 2.0\n    )\n\n    wires wires()\n\n    label stroke(3.48 * units.inch)\n    label bore(3.5 * units.inch)\n    label rod_length(5.142 * units.inch)\n    label rod_mass(535 * units.g)\n    label compression_height(1.0 * units.inch)\n    label crank_mass(50 * units.lb)\n    label flywheel_mass(30 * units.lb)\n    label flywheel_radius(6 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 10 * units.kg, radius: 1.0 * units.cm)\n    )\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 5.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: 45 * units.deg\n    )\n\n    rod_journal rj0(angle: 0.0 * units.deg)\n    rod_journal rj1(angle: 120.0 * units.deg)\n    rod_journal rj2(angle: 240.0 * units.deg)\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n\n    piston_parameters piston_params(\n        mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: rod_mass,\n        moment_of_inertia: rod_moment_of_inertia(\n            mass: rod_mass,\n            length: rod_length\n        ),\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 1.325 * units.L,\n        plenum_cross_section_area: 20.0 * units.cm2,\n        intake_flow_rate: k_carb(400.0),\n        runner_flow_rate: k_carb(250.0),\n        runner_length: 4.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.995,\n        throttle_gamma: 2.0,\n        velocity_decay: 0.5\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(1000.0),\n        primary_tube_length: 20.0 * units.inch,\n        primary_flow_rate: k_carb(600.0),\n        velocity_decay: 1.0\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        length: 100 * units.inch,\n        audio_volume: 1.0,\n        impulse_response: ir_lib.mild_exhaust_0_reverb\n    )\n    exhaust_system exhaust1(\n        es_params,\n        length: 172 * units.inch,\n        audio_volume: 1.0,\n        impulse_response: ir_lib.mild_exhaust_0_reverb\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    label spacing(5.0 * units.inch)\n\n    cylinder_bank b0(bank_params, angle: -45.0 * units.deg)\n    cylinder_bank b1(bank_params, angle: 45.0 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1,\n            sound_attenuation: 1.0,\n            primary_length: spacing * 2\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.05)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3,\n            sound_attenuation: 1.0,\n            primary_length: spacing * 1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire5,\n            primary_length: spacing * 0\n        )\n        .set_cylinder_head(\n            odd_fire_v6_head(\n                intake_camshaft: camshaft.intake_cam_0,\n                exhaust_camshaft: camshaft.exhaust_cam_0)\n        )\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2,\n            sound_attenuation: 1.0,\n            primary_length: spacing * 2\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4,\n            sound_attenuation: 1.0,\n            primary_length: spacing * 1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire6,\n            sound_attenuation: 1.0,\n            primary_length: spacing * 0\n        )\n        .set_cylinder_head(\n            odd_fire_v6_head(\n                intake_camshaft: camshaft.intake_cam_1,\n                exhaust_camshaft: camshaft.exhaust_cam_1,\n                flip_display: true)\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 222 * units.deg,\n        gamma: 1.0,\n        lift: 400 * units.thou,\n        steps: 100\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 226 * units.deg,\n        gamma: 1.0,\n        lift: 300 * units.thou,\n        steps: 100\n    )\n\n    odd_fire_v6_camshaft camshaft(\n        lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 117 * units.deg,\n        exhaust_lobe_center: 112 * units.deg,\n        base_radius: 0.75 * units.inch\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(1000 * units.rpm, 20 * units.deg)\n        .add_sample(2000 * units.rpm, 25 * units.deg)\n        .add_sample(3000 * units.rpm, 30 * units.deg)\n        .add_sample(4000 * units.rpm, 30 * units.deg)\n\n    ignition_module ignition_module(\n        timing_curve: timing_curve,\n        rev_limit: 5600 * units.rpm,\n        limiter_duration: 0.2)\n    ignition_module\n            .connect_wire(wires.wire1, 0 * units.deg)\n            .connect_wire(wires.wire6, 150 * units.deg)\n            .connect_wire(wires.wire5, 240 * units.deg)\n            .connect_wire(wires.wire4, 390 * units.deg)\n            .connect_wire(wires.wire3, 480 * units.deg)\n            .connect_wire(wires.wire2, 630 * units.deg)\n\n    engine.add_ignition_module(ignition_module)\n}\n\nlabel car_mass(2700 * units.lb)\nprivate node random_car {\n    alias output __out:\n        vehicle(\n            mass: car_mass,\n            drag_coefficient: 0.3,\n            cross_sectional_area: (72 * units.inch) * (56 * units.inch),\n            diff_ratio: 3.9,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 0.015 * car_mass * 9.81\n        );\n}\n\nprivate node random_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 300 * units.lb_ft\n        )\n        .add_gear(3.636)\n        .add_gear(2.375)\n        .add_gear(1.761)\n        .add_gear(1.346)\n        .add_gear(0.971)\n        .add_gear(0.756);\n}\n\npublic node main {\n    set_engine(odd_fire_v6_90())\n    set_vehicle(random_car())\n    set_transmission(random_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-2/06_even_fire_v6.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\nlabel cycle(2 * 360 * units.deg)\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n    output wire6: ignition_wire();\n}\n\nprivate node even_fire_v6_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 67 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 1.35 * units.inch * 1.35 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 2.0 * units.inch * 2.0 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 58 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 103 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 156 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 214 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 249 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 268 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 281 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 37 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 72 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 113 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 196 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 222 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 245 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 246 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: standard_valvetrain(\n            intake_camshaft: intake_camshaft,\n            exhaust_camshaft: exhaust_camshaft\n        ),\n        flip_display: flip_display\n    )\n}\n\nprivate node even_fire_v6_camshaft {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;  \n    input advance: 0 * units.deg; \n    input base_radius: 0.5 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output exhaust_cam_0: _exhaust_cam_0;\n\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot180(180 * units.deg)\n    label rot360(360 * units.deg)\n\n    // 1 6 5 4 3 2\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center + 0 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 480 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 240 * units.deg)\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center + 0 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 480 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 240 * units.deg)\n\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + 600 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 360 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 120 * units.deg)\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + 600 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 360 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 120 * units.deg)\n}\n\npublic node even_fire_v6_90 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Generic Even-fire V6 (Split Rod Jnl.)\",\n        starter_torque: 70 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 5500 * units.rpm,\n        fuel: fuel(\n            //max_turbulence_effect: 2.5,\n            //burning_efficiency_randomness: 0.5,\n            //max_burning_efficiency: 0.75\n        ),\n        throttle_gamma: 2.0\n    )\n\n    wires wires()\n\n    label stroke(3.48 * units.inch)\n    label bore(3.5 * units.inch)\n    label rod_length(5.142 * units.inch)\n    label rod_mass(535 * units.g)\n    label compression_height(1.0 * units.inch)\n    label crank_mass(50 * units.lb)\n    label flywheel_mass(30 * units.lb)\n    label flywheel_radius(6 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 10 * units.kg, radius: 1.0 * units.cm)\n    )\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 5.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: 45 * units.deg\n    )\n\n    rod_journal rj0(angle: 0.0 * units.deg)\n    rod_journal rj1(angle: (0.0 - 30) * units.deg)\n    rod_journal rj2(angle: 120.0 * units.deg)\n    rod_journal rj3(angle: (120.0 - 30) * units.deg)\n    rod_journal rj4(angle: 240.0 * units.deg)\n    rod_journal rj5(angle: (240.0 - 30) * units.deg)\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n        .add_rod_journal(rj4)\n        .add_rod_journal(rj5)\n\n    piston_parameters piston_params(\n        mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: rod_mass,\n        moment_of_inertia: rod_moment_of_inertia(\n            mass: rod_mass,\n            length: rod_length\n        ),\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 1.325 * units.L,\n        plenum_cross_section_area: 20.0 * units.cm2,\n        intake_flow_rate: k_carb(400.0),\n        runner_flow_rate: k_carb(250.0),\n        runner_length: 4.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.995,\n        velocity_decay: 0.5\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(1000.0),\n        primary_tube_length: 20.0 * units.inch,\n        primary_flow_rate: k_carb(300.0),\n        velocity_decay: 1.0,\n        length: 100.0 * units.inch\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        length: 100.0 * units.inch,\n        audio_volume: 1.0,\n        impulse_response: ir_lib.mild_exhaust_0_reverb\n    )\n    exhaust_system exhaust1(\n        es_params,\n        length: 172.0 * units.inch,\n        audio_volume: 1.0,\n        impulse_response: ir_lib.mild_exhaust_0_reverb\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    label spacing(5.0 * units.inch)\n\n    cylinder_bank b0(bank_params, angle: -45.0 * units.deg)\n    cylinder_bank b1(bank_params, angle: 45.0 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1,\n            sound_attenuation: 0.8,\n            primary_length: spacing * 2\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3,\n            sound_attenuation: 0.9,\n            primary_length: spacing * 1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj4,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire5,\n            primary_length: spacing * 0\n        )\n        .set_cylinder_head(\n            even_fire_v6_head(\n                intake_camshaft: camshaft.intake_cam_0,\n                exhaust_camshaft: camshaft.exhaust_cam_0)\n        )\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2,\n            sound_attenuation: 0.6,\n            primary_length: spacing * 2\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4,\n            sound_attenuation: 0.3,\n            primary_length: spacing * 1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj5,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire6,\n            sound_attenuation: 1.1,\n            primary_length: spacing * 0\n        )\n        .set_cylinder_head(\n            even_fire_v6_head(\n                intake_camshaft: camshaft.intake_cam_1,\n                exhaust_camshaft: camshaft.exhaust_cam_1,\n                flip_display: true)\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 222 * units.deg,\n        gamma: 1.0,\n        lift: 400 * units.thou,\n        steps: 100\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 226 * units.deg,\n        gamma: 1.0,\n        lift: 300 * units.thou,\n        steps: 100\n    )\n\n    even_fire_v6_camshaft camshaft(\n        lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 117 * units.deg,\n        exhaust_lobe_center: 112 * units.deg,\n        base_radius: 0.75 * units.inch\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(1000 * units.rpm, 20 * units.deg)\n        .add_sample(2000 * units.rpm, 25 * units.deg)\n        .add_sample(3000 * units.rpm, 30 * units.deg)\n        .add_sample(4000 * units.rpm, 30 * units.deg)\n\n    ignition_module ignition_module(\n        timing_curve: timing_curve,\n        rev_limit: 5600 * units.rpm,\n        limiter_duration: 0.2)\n    ignition_module\n            .connect_wire(wires.wire1, 0 * units.deg)\n            .connect_wire(wires.wire6, 120 * units.deg)\n            .connect_wire(wires.wire5, 240 * units.deg)\n            .connect_wire(wires.wire4, 360 * units.deg)\n            .connect_wire(wires.wire3, 480 * units.deg)\n            .connect_wire(wires.wire2, 600 * units.deg)\n\n    engine.add_ignition_module(ignition_module)\n}\n\nlabel car_mass(2700 * units.lb)\nprivate node random_car {\n    alias output __out:\n        vehicle(\n            mass: car_mass,\n            drag_coefficient: 0.3,\n            cross_sectional_area: (72 * units.inch) * (56 * units.inch),\n            diff_ratio: 3.9,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 0.015 * car_mass * 9.81\n        );\n}\n\nprivate node random_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 300 * units.lb_ft\n        )\n        .add_gear(3.636)\n        .add_gear(2.375)\n        .add_gear(1.761)\n        .add_gear(1.346)\n        .add_gear(0.971)\n        .add_gear(0.756);\n}\n\npublic node main {\n    set_engine(even_fire_v6_90())\n    set_vehicle(random_car())\n    set_transmission(random_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-2/07_gm_ls.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\nlabel cycle(2 * 360 * units.deg)\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n    output wire6: ignition_wire();\n    output wire7: ignition_wire();\n    output wire8: ignition_wire();\n}\n\nprivate node ls_v8_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 90 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 2.2 * units.inch * 2.2 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 1 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 103 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 156 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 214 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 249 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 268 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 281 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 1 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 72 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 113 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 196 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 222 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 245 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 246 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: standard_valvetrain(\n            intake_camshaft: intake_camshaft,\n            exhaust_camshaft: exhaust_camshaft\n        ),\n        flip_display: flip_display\n    )\n}\n\nprivate node ls_v8_camshaft {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;  \n    input advance: 0 * units.deg; \n    input base_radius: 0.5 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output exhaust_cam_0: _exhaust_cam_0;\n\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot(90 * units.deg)\n    label rot360(360 * units.deg)\n\n    // 1 8 7 2 6 5 4 3\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center + 0 * rot) // 1\n        .add_lobe(rot360 - exhaust_lobe_center + 7 * rot) // 3\n        .add_lobe(rot360 - exhaust_lobe_center + 5 * rot) // 5\n        .add_lobe(rot360 - exhaust_lobe_center + 2 * rot) // 7\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center + 0 * rot) // 1\n        .add_lobe(rot360 + intake_lobe_center + 7 * rot) // 3\n        .add_lobe(rot360 + intake_lobe_center + 5 * rot) // 5\n        .add_lobe(rot360 + intake_lobe_center + 2 * rot) // 7\n\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + 3 * rot) // 2\n        .add_lobe(rot360 - exhaust_lobe_center + 6 * rot) // 4\n        .add_lobe(rot360 - exhaust_lobe_center + 4 * rot) // 6\n        .add_lobe(rot360 - exhaust_lobe_center + 1 * rot) // 8\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + 3 * rot) // 2\n        .add_lobe(rot360 + intake_lobe_center + 6 * rot) // 4\n        .add_lobe(rot360 + intake_lobe_center + 4 * rot) // 6\n        .add_lobe(rot360 + intake_lobe_center + 1 * rot) // 8\n}\n\nprivate node turbulence_to_flame_speed_ratio {\n    alias output __out:\n        function(5.0)\n            .add_sample(0.0, 3.0)\n            .add_sample(5.0, 1.5 * 5.0)\n            .add_sample(10.0, 1.75 * 10.0)\n            .add_sample(15.0, 2.0 * 15.0)\n            .add_sample(20.0, 2.0 * 20.0)\n            .add_sample(25.0, 2.0 * 25.0)\n            .add_sample(30.0, 2.0 * 30.0)\n            .add_sample(35.0, 2.0 * 35.0)\n            .add_sample(40.0, 2.0 * 40.0)\n            .add_sample(45.0, 2.0 * 45.0);\n}\n\npublic node ls_v8 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"GM LS\",\n        starter_torque: 200 * units.lb_ft,\n        starter_speed: 200 * units.rpm,\n        redline: 6500 * units.rpm,\n        throttle_gamma: 2.0,\n        fuel: fuel(\n            //max_turbulence_effect: 10.0,\n            //max_dilution_effect: 20.0,R\n            //burning_efficiency_randomness: 0.5,\n            max_burning_efficiency: 1.0,\n            turbulence_to_flame_speed_ratio: turbulence_to_flame_speed_ratio()\n        ),\n        hf_gain: 0.01,\n        noise: 1.0,\n        jitter: 0.6,\n        simulation_frequency: 10000\n    )\n\n    wires wires()\n\n    label stroke(3.622 * units.inch)\n    label bore(3.78 * units.inch)\n    label rod_length(160 * units.mm)\n    label rod_mass(50 * units.g)\n    label compression_height(1.0 * units.inch)\n    label crank_mass(60 * units.lb)\n    label flywheel_mass(30 * units.lb)\n    label flywheel_radius(8 * units.inch)\n\n    label crank_moment(\n        1.5 * disk_moment_of_inertia(mass: crank_mass, radius: stroke)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 1 * units.kg, radius: 1.0 * units.cm)\n    )\n\n    label v_angle(90 * units.deg)\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 20.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: 90 * units.deg - (v_angle / 2.0)\n    )\n\n    // 1 8 7 2 6 5 4 3\n    rod_journal rj0(angle: 0 * units.deg)\n    rod_journal rj1(angle: 270 * units.deg)\n    rod_journal rj2(angle: 90 * units.deg)\n    rod_journal rj3(angle: 180 * units.deg)\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n\n    piston_parameters piston_params(\n         // 414 - piston mass, 152 - pin weight\n        mass: (100) * units.g,\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: rod_mass,\n        moment_of_inertia: rod_moment_of_inertia(\n            mass: rod_mass,\n            length: rod_length\n        ),\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 1.325 * units.L,\n        plenum_cross_section_area: 20.0 * units.cm2,\n        intake_flow_rate: k_carb(700.0),\n        runner_flow_rate: k_carb(100.0),\n        runner_length: 12.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.996,\n        velocity_decay: 0.5\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(1000.0),\n        primary_tube_length: 29.0 * units.inch,\n        primary_flow_rate: k_carb(500.0),\n        velocity_decay: 1.0\n    )\n    \n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 4.0,\n        length: 100 * units.inch,\n        impulse_response: ir_lib.default_0\n\n    )\n    exhaust_system exhaust1(\n        es_params,\n        audio_volume: 4.0,\n        length: 172 * units.inch,\n        impulse_response: ir_lib.default_0\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    label spacing(2 * units.inch)\n\n    cylinder_bank b0(bank_params, angle: -v_angle / 2.0)\n    cylinder_bank b1(bank_params, angle: v_angle / 2.0)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1,\n            sound_attenuation: 1.0,\n            primary_length: 3 * spacing + 2 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3,\n            sound_attenuation: 1.0,\n            primary_length: 2 * spacing + 1 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire5,\n            sound_attenuation: 1.0,\n            primary_length: 1 * spacing + 3 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire7,\n            sound_attenuation: 1.0,\n            primary_length: 0 * spacing + 5 * units.cm\n        )\n        .set_cylinder_head(\n            ls_v8_head(\n                intake_camshaft: camshaft.intake_cam_0,\n                exhaust_camshaft: camshaft.exhaust_cam_0,\n                flip_display: false,\n                flow_attenuation: 1.0)\n        )\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2,\n            sound_attenuation: 1.0,\n            primary_length: 3 * spacing + 1 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4,\n            sound_attenuation: 1.0,\n            primary_length: 2 * spacing + 5 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire6,\n            sound_attenuation: 1.0,\n            primary_length: 1 * spacing + 7 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire8,\n            sound_attenuation: 1.0,\n            primary_length: 0 * spacing + 0 * units.cm\n        )\n        .set_cylinder_head(\n            ls_v8_head(\n                intake_camshaft: camshaft.intake_cam_1,\n                exhaust_camshaft: camshaft.exhaust_cam_1,\n                flow_attenuation: 1.0,\n                flip_display: true)\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 234 * units.deg,\n        gamma: 1.1,\n        lift: 551 * units.thou,\n        steps: 256\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 235 * units.deg,\n        gamma: 1.1,\n        lift: 551 * units.thou,\n        steps: 256\n    )\n\n    ls_v8_camshaft camshaft(\n        lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 116 * units.deg,\n        exhaust_lobe_center: 116 * units.deg,\n        base_radius: 1.0 * units.inch\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(1000 * units.rpm, 12 * units.deg)\n        .add_sample(2000 * units.rpm, 20 * units.deg)\n        .add_sample(3000 * units.rpm, 30 * units.deg)\n        .add_sample(4000 * units.rpm, 40 * units.deg)\n        .add_sample(5000 * units.rpm, 40 * units.deg)\n        .add_sample(6000 * units.rpm, 40 * units.deg)\n        .add_sample(7000 * units.rpm, 40 * units.deg)\n        .add_sample(8000 * units.rpm, 40 * units.deg)\n\n    ignition_module ignition_module(\n        timing_curve: timing_curve,\n        rev_limit: 6800 * units.rpm,\n        limiter_duration: 0.2)\n    ignition_module\n            .connect_wire(wires.wire1,  0 * 90 * units.deg)\n            .connect_wire(wires.wire8,  1 * 90 * units.deg)\n            .connect_wire(wires.wire7,  2 * 90 * units.deg)\n            .connect_wire(wires.wire2,  3 * 90 * units.deg)\n            .connect_wire(wires.wire6,  4 * 90 * units.deg)\n            .connect_wire(wires.wire5,  5 * 90 * units.deg)\n            .connect_wire(wires.wire4,  6 * 90 * units.deg)\n            .connect_wire(wires.wire3,  7 * 90 * units.deg)\n\n    engine.add_ignition_module(ignition_module)\n}\n\nprivate node corvette {\n    alias output __out:\n        vehicle(\n            mass: 1614 * units.kg,\n            drag_coefficient: 0.3,\n            cross_sectional_area: (72 * units.inch) * (50 * units.inch),\n            diff_ratio: 3.42,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 200 * units.N\n        );\n}\n\nprivate node corvette_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 500 * units.lb_ft\n        )\n        .add_gear(2.97)\n        .add_gear(2.07)\n        .add_gear(1.43)\n        .add_gear(1.00)\n        .add_gear(0.71)\n        .add_gear(0.57);\n}\n\npublic node main {\n    set_engine(ls_v8())\n    set_vehicle(corvette())\n    set_transmission(corvette_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-2/08_ferrari_f136_v8.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\nlabel cycle(2 * 360 * units.deg)\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n    output wire6: ignition_wire();\n    output wire7: ignition_wire();\n    output wire8: ignition_wire();\n}\n\nprivate node f136_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 90 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 2.2 * units.inch * 2.2 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 58 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 103 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 156 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 214 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 249 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 268 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 281 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 37 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 72 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 113 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 196 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 222 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 245 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 246 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: standard_valvetrain(\n            intake_camshaft: intake_camshaft,\n            exhaust_camshaft: exhaust_camshaft\n        ),\n        flip_display: flip_display\n    )\n}\n\nprivate node f136_camshaft {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;  \n    input advance: 0 * units.deg; \n    input base_radius: 0.5 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output exhaust_cam_0: _exhaust_cam_0;\n\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot(90 * units.deg)\n    label rot360(360 * units.deg)\n\n    // 1 5 3 7 4 8 2 6\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center + 0 * rot) // 1\n        .add_lobe(rot360 - exhaust_lobe_center + 6 * rot) // 2\n        .add_lobe(rot360 - exhaust_lobe_center + 2 * rot) // 3\n        .add_lobe(rot360 - exhaust_lobe_center + 4 * rot) // 4\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center + 0 * rot) // 1\n        .add_lobe(rot360 + intake_lobe_center + 6 * rot) // 2\n        .add_lobe(rot360 + intake_lobe_center + 2 * rot) // 3\n        .add_lobe(rot360 + intake_lobe_center + 4 * rot) // 4\n\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + 1 * rot) // 5\n        .add_lobe(rot360 - exhaust_lobe_center + 7 * rot) // 6\n        .add_lobe(rot360 - exhaust_lobe_center + 3 * rot) // 7\n        .add_lobe(rot360 - exhaust_lobe_center + 5 * rot) // 8\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + 1 * rot) // 5\n        .add_lobe(rot360 + intake_lobe_center + 7 * rot) // 6\n        .add_lobe(rot360 + intake_lobe_center + 3 * rot) // 7\n        .add_lobe(rot360 + intake_lobe_center + 5 * rot) // 8\n}\n\nprivate node turbulence_to_flame_speed_ratio {\n    alias output __out:\n        function(5.0)\n            .add_sample(0.0, 3.0)\n            .add_sample(5.0, 1.5 * 5.0)\n            .add_sample(10.0, 1.75 * 10.0)\n            .add_sample(15.0, 2.0 * 15.0)\n            .add_sample(20.0, 2.0 * 20.0)\n            .add_sample(25.0, 2.0 * 25.0)\n            .add_sample(30.0, 2.0 * 30.0)\n            .add_sample(35.0, 2.0 * 35.0)\n            .add_sample(40.0, 2.0 * 40.0)\n            .add_sample(45.0, 2.0 * 45.0);\n}\n\npublic node f136_v8 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Ferrari F136\",\n        starter_torque: 200 * units.lb_ft,\n        starter_speed: 200 * units.rpm,\n        redline: 9000 * units.rpm,\n        throttle_gamma: 2.0,\n        fuel: fuel(\n            max_burning_efficiency: 1.0,\n            turbulence_to_flame_speed_ratio: turbulence_to_flame_speed_ratio()\n        ),\n        hf_gain: 0.01,\n        noise: 1.0,\n        jitter: 0.15,\n        simulation_frequency: 10000\n    )\n\n    wires wires()\n\n    label stroke(81 * units.mm)\n    label bore(94 * units.mm)\n    label rod_length(160 * units.mm)\n    label rod_mass(50 * units.g)\n    label compression_height(1.0 * units.inch)\n    label crank_mass(60 * units.lb)\n    label flywheel_mass(30 * units.lb)\n    label flywheel_radius(8 * units.inch)\n\n    label crank_moment(\n        1.5 * disk_moment_of_inertia(mass: crank_mass, radius: stroke)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 1 * units.kg, radius: 1.0 * units.cm)\n    )\n\n    label v_angle(90 * units.deg)\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 20.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: 90 * units.deg + (v_angle / 2.0)\n    )\n\n    // 1 5 3 7 4 8 2 6\n    rod_journal rj0(angle: 0 * units.deg)\n    rod_journal rj1(angle: 180 * units.deg)\n    rod_journal rj2(angle: 180 * units.deg)\n    rod_journal rj3(angle: 0 * units.deg)\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n\n    piston_parameters piston_params(\n         // 414 - piston mass, 152 - pin weight\n        mass: (100) * units.g,\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: rod_mass,\n        moment_of_inertia: rod_moment_of_inertia(\n            mass: rod_mass,\n            length: rod_length\n        ),\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 1.325 * units.L,\n        plenum_cross_section_area: 20.0 * units.cm2,\n        intake_flow_rate: k_carb(700.0),\n        runner_flow_rate: k_carb(100.0),\n        runner_length: 12.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.995,\n        velocity_decay: 0.5\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(1000.0),\n        primary_tube_length: 29.0 * units.inch,\n        primary_flow_rate: k_carb(600.0),\n        velocity_decay: 0.5\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 2.0 * 0.1,\n        length: 100 * units.inch,\n        impulse_response: ir_lib.mild_exhaust_0_reverb\n\n    )\n    exhaust_system exhaust1(\n        es_params,\n        audio_volume: 2.0 * 0.09,\n        length: 100 * units.inch,\n        impulse_response: ir_lib.mild_exhaust_0_reverb\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    label spacing(5 * units.inch)\n\n    cylinder_bank b0(bank_params, angle: v_angle / 2.0)\n    cylinder_bank b1(bank_params, angle: -v_angle / 2.0)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1,\n            sound_attenuation: 0.9,\n            primary_length: 2 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire2,\n            sound_attenuation: 0.8,\n            primary_length: 1 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3,\n            sound_attenuation: 1.1,\n            primary_length: 3 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire4,\n            sound_attenuation: 1.0,\n            primary_length: 5 * units.cm\n        )\n        .set_cylinder_head(\n            f136_head(\n                intake_camshaft: camshaft.intake_cam_0,\n                exhaust_camshaft: camshaft.exhaust_cam_0,\n                flip_display: true,\n                flow_attenuation: 1.0)\n        )\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire5,\n            sound_attenuation: 1.0,\n            primary_length: 1 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire6,\n            sound_attenuation: 0.8,\n            primary_length: 5 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire7,\n            sound_attenuation: 0.9,\n            primary_length: 7 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire8,\n            sound_attenuation: 0.7,\n            primary_length: 0 * units.cm\n        )\n        .set_cylinder_head(\n            f136_head(\n                intake_camshaft: camshaft.intake_cam_1,\n                exhaust_camshaft: camshaft.exhaust_cam_1,\n                flow_attenuation: 1.0)\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 230 * units.deg,\n        gamma: 0.9,\n        lift: 551 * units.thou,\n        steps: 256\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 230 * units.deg,\n        gamma: 0.9,\n        lift: 551 * units.thou,\n        steps: 256\n    )\n\n    f136_camshaft camshaft(\n        lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 116 * units.deg,\n        exhaust_lobe_center: 116 * units.deg,\n        base_radius: 1.0 * units.inch\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(1000 * units.rpm, 12 * units.deg)\n        .add_sample(2000 * units.rpm, 20 * units.deg)\n        .add_sample(3000 * units.rpm, 30 * units.deg)\n        .add_sample(4000 * units.rpm, 40 * units.deg)\n        .add_sample(5000 * units.rpm, 40 * units.deg)\n        .add_sample(6000 * units.rpm, 40 * units.deg)\n        .add_sample(7000 * units.rpm, 40 * units.deg)\n        .add_sample(8000 * units.rpm, 40 * units.deg)\n\n    ignition_module ignition_module(\n        timing_curve: timing_curve,\n        rev_limit: 9300 * units.rpm,\n        limiter_duration: 0.1)\n    ignition_module\n            .connect_wire(wires.wire1,  0 * 90 * units.deg)\n            .connect_wire(wires.wire5,  1 * 90 * units.deg)\n            .connect_wire(wires.wire3,  2 * 90 * units.deg)\n            .connect_wire(wires.wire7,  3 * 90 * units.deg)\n            .connect_wire(wires.wire4,  4 * 90 * units.deg)\n            .connect_wire(wires.wire8,  5 * 90 * units.deg)\n            .connect_wire(wires.wire2,  6 * 90 * units.deg)\n            .connect_wire(wires.wire6,  7 * 90 * units.deg)\n\n    engine.add_ignition_module(ignition_module)\n}\n\nprivate node mustang_vehicle {\n    alias output __out:\n        vehicle(\n            mass: 1614 * units.kg,\n            drag_coefficient: 0.3,\n            cross_sectional_area: (72 * units.inch) * (50 * units.inch),\n            diff_ratio: 3.42,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 200 * units.N\n        );\n}\n\nprivate node mustang_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 500 * units.lb_ft\n        )\n        .add_gear(3.23)\n        .add_gear(2.19)\n        .add_gear(1.61)\n        .add_gear(1.23)\n        .add_gear(0.97)\n        .add_gear(0.8);\n}\n\npublic node main {\n    set_engine(f136_v8())\n    set_vehicle(mustang_vehicle())\n    set_transmission(mustang_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-2/09_radial_9.mr",
    "content": "import \"engine_sim.mr\"\n\nimport \"radial.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n    output wire6: ignition_wire();\n    output wire7: ignition_wire();\n    output wire8: ignition_wire();\n    output wire9: ignition_wire();\n}\n\nlabel cycle(2 * 360 * units.deg)\npublic node radial_9_distributor {\n    input wires;\n    input timing_curve;\n    input rev_limit: 5500 * units.rpm;\n    alias output __out:\n        ignition_module(\n            timing_curve: timing_curve,\n            rev_limit: rev_limit,\n            limiter_duration: 0.2\n        )\n            .connect_wire(wires.wire1, (0 / 9.0) * cycle)\n            .connect_wire(wires.wire3, (1 / 9.0) * cycle)\n            .connect_wire(wires.wire5, (2 / 9.0) * cycle)\n            .connect_wire(wires.wire7, (3 / 9.0) * cycle)\n            .connect_wire(wires.wire9, (4 / 9.0) * cycle)\n            .connect_wire(wires.wire2, (5 / 9.0) * cycle)\n            .connect_wire(wires.wire4, (6 / 9.0) * cycle)\n            .connect_wire(wires.wire6, (7 / 9.0) * cycle)\n            .connect_wire(wires.wire8, (8 / 9.0) * cycle);\n}\n\npublic node radial_9 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Radial 9\",\n        starter_torque: 80 * units.lb_ft,\n        starter_speed: 400 * units.rpm,\n        redline: 3000 * units.rpm,\n        fuel: fuel(\n            //max_turbulence_effect: 0.5,\n            //max_burning_efficiency: 1.0\n        ),\n        simulation_frequency: 7500\n    )\n\n    wires wires()\n\n    label slave_throw(3.5 * units.inch)\n    label stroke(5.5 * units.inch)\n    label bore(5 * units.inch)\n    label rod_length(16 * units.inch)\n    label compression_height(1.0 * units.inch)\n    label rod_mass(535 * units.g)\n    label crank_mass(20.39 * units.kg)\n    label flywheel_mass(50 * units.kg)\n    label flywheel_radius(12 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 10 * units.kg, radius: 2.0 * units.cm)\n    )\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: 10 * units.lb,\n        mass: 10 * units.lb,\n        friction_torque: 10.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: (90 - 0.5 * 45) * units.deg\n    )\n\n    rod_journal rj0(angle: 0.0)\n    c0\n        .add_rod_journal(rj0)\n\n    piston_parameters piston_params(\n        mass: 10 * units.g,\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: 100.0 * units.g,\n        moment_of_inertia: 0.0015884918028487504,\n        center_of_mass: 0.0,\n        length: rod_length - slave_throw\n    )\n\n    intake intake(\n        plenum_volume: 10.5 * units.L,\n        plenum_cross_section_area: 10.0 * units.cm2,\n        intake_flow_rate: k_carb(1000.0),\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.993,\n        throttle_gamma: 1.0,\n        velocity_decay: 1.0\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(2000.0),\n        primary_tube_length: 70.0 * units.inch,\n        primary_flow_rate: k_carb(1000.0),\n        velocity_decay: 0.75,\n        length: 100 * units.inch\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 1.0,\n        impulse_response: ir_lib.mild_exhaust_0_reverb\n    )\n\n    exhaust_system exhaust1(\n        es_params,\n        audio_volume: 1.0,\n        impulse_response: ir_lib.mild_exhaust_0_reverb\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    connecting_rod master(\n        connecting_rod_parameters(\n            cr_params,\n            slave_throw: slave_throw,\n            length: rod_length\n        )\n    )\n\n    rod_journal sj0(angle: (0 / 9.0) * 360 * units.deg)\n    rod_journal sj1(angle: (1 / 9.0) * 360 * units.deg)\n    rod_journal sj2(angle: (2 / 9.0) * 360 * units.deg)\n    rod_journal sj3(angle: (3 / 9.0) * 360 * units.deg)\n    rod_journal sj4(angle: (4 / 9.0) * 360 * units.deg)\n    rod_journal sj5(angle: (5 / 9.0) * 360 * units.deg)\n    rod_journal sj6(angle: (6 / 9.0) * 360 * units.deg)\n    rod_journal sj7(angle: (7 / 9.0) * 360 * units.deg)\n    rod_journal sj8(angle: (8 / 9.0) * 360 * units.deg)\n    master\n        .add_slave_journal(sj0)\n        .add_slave_journal(sj1)\n        .add_slave_journal(sj2)\n        .add_slave_journal(sj3)\n        .add_slave_journal(sj4)\n        .add_slave_journal(sj5)\n        .add_slave_journal(sj6)\n        .add_slave_journal(sj7)\n        .add_slave_journal(sj8)\n\n    label spacing(10 * units.inch)\n\n    cylinder_bank b0(bank_params, angle: (0 / 9.0) * 360 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: master,\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1,\n            primary_length: 6.11 * units.foot\n        )\n\n    cylinder_bank b1(bank_params, angle: (1 / 9.0) * 360 * units.deg)\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.03)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: sj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire9,\n            primary_length: 7.46 * units.foot\n        )\n\n    cylinder_bank b2(bank_params, angle: (2 / 9.0) * 360 * units.deg)\n    b2\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: sj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire8,\n            primary_length: 8.31 * units.foot\n        )\n\n    cylinder_bank b3(bank_params, angle: (3 / 9.0) * 360 * units.deg)\n    b3\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: sj3,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire7,\n            primary_length: 8.45 * units.foot\n        )\n    cylinder_bank b4(bank_params, angle: (4 / 9.0) * 360 * units.deg)\n    b4\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.5)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: sj4,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire6,\n            primary_length: 7.84 * units.foot\n        )\n    cylinder_bank b5(bank_params, angle: (5 / 9.0) * 360 * units.deg)\n    b5\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.5)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: sj5,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire5,\n            primary_length: 6.63 * units.foot\n        )\n    cylinder_bank b6(bank_params, angle: (6 / 9.0) * 360 * units.deg)\n    b6\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.5)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: sj6,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4,\n            primary_length: 5.2 * units.foot\n        )\n    cylinder_bank b7(bank_params, angle: (7 / 9.0) * 360 * units.deg)\n    b7\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.5)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: sj7,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire3,\n            primary_length: 4.33 * units.foot\n        )\n    cylinder_bank b8(bank_params, angle: (8 / 9.0) * 360 * units.deg)\n    b8\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.5)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: sj8,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2,\n            primary_length: 4.77 * units.foot\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n        .add_cylinder_bank(b2)\n        .add_cylinder_bank(b3)\n        .add_cylinder_bank(b4)\n        .add_cylinder_bank(b5)\n        .add_cylinder_bank(b6)\n        .add_cylinder_bank(b7)\n        .add_cylinder_bank(b8)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe lobe(\n        duration_at_50_thou: 260 * units.deg,\n        gamma: 0.9,\n        lift: 800 * units.thou,\n        steps: 100\n    )\n\n    b0.set_cylinder_head (\n        radial_head(\n            offset: 0 / 9.0,\n            lobe_profile: lobe\n        )\n    )\n\n    b1.set_cylinder_head (\n        radial_head(\n            offset: 4 / 9.0,\n            lobe_profile: lobe\n        )\n    )\n\n    b2.set_cylinder_head (\n        radial_head(\n            offset: 8 / 9.0,\n            lobe_profile: lobe\n        )\n    )\n\n    b3.set_cylinder_head (\n        radial_head(\n            offset: 3 / 9.0,\n            lobe_profile: lobe\n        )\n    )\n\n    b4.set_cylinder_head (\n        radial_head(\n            offset: 7 / 9.0,\n            lobe_profile: lobe\n        )\n    )\n\n    b5.set_cylinder_head (\n        radial_head(\n            offset: 2 / 9.0,\n            lobe_profile: lobe\n        )\n    )\n\n    b6.set_cylinder_head (\n        radial_head(\n            offset: 6 / 9.0,\n            lobe_profile: lobe\n        )\n    )\n\n    b7.set_cylinder_head (\n        radial_head(\n            offset: 1 / 9.0,\n            lobe_profile: lobe\n        )\n    )\n\n    b8.set_cylinder_head (\n        radial_head(\n            offset: 5 / 9.0,\n            lobe_profile: lobe\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 18 * units.deg)\n        .add_sample(1000 * units.rpm, 18 * units.deg)\n        .add_sample(2000 * units.rpm, 30 * units.deg)\n        .add_sample(3000 * units.rpm, 40 * units.deg)\n        .add_sample(4000 * units.rpm, 40 * units.deg)\n\n    engine.add_ignition_module(\n        radial_9_distributor(\n            wires: wires,\n            timing_curve: timing_curve,\n            rev_limit: 3500 * units.rpm\n        ))\n}\n\nlabel car_mass(2700 * units.lb)\nprivate node random_car {\n    alias output __out:\n        vehicle(\n            mass: car_mass,\n            drag_coefficient: 0.3,\n            cross_sectional_area: (72 * units.inch) * (56 * units.inch),\n            diff_ratio: 3.9,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 10000\n        );\n}\n\nprivate node random_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 2000 * units.lb_ft\n        )\n        .add_gear(0.01);\n}\n\npublic node main {\n    set_engine(radial_9())\n    set_vehicle(random_car())\n    set_transmission(random_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-2/10_lfa_v10.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\nlabel cycle(2 * 360 * units.deg)\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n    output wire6: ignition_wire();\n    output wire7: ignition_wire();\n    output wire8: ignition_wire();\n    output wire9: ignition_wire();\n    output wire10: ignition_wire();\n}\n\nprivate node v10_72_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 1.5 * 25 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 2.5 * units.inch * 2.5 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 58 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 103 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 156 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 214 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 249 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 268 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 281 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 37 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 72 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 113 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 196 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 222 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 245 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 246 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: standard_valvetrain(\n            intake_camshaft: intake_camshaft,\n            exhaust_camshaft: exhaust_camshaft\n        ),\n        flip_display: flip_display\n    )\n}\n\nprivate node v10_72_camshaft {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;  \n    input advance: 0 * units.deg; \n    input base_radius: 0.5 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output exhaust_cam_0: _exhaust_cam_0;\n\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot(72 * units.deg)\n    label rot360(360 * units.deg)\n\n    // 1 2 3 4 7 8 9 10 5 6\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center + 0 * rot) // 1\n        .add_lobe(rot360 - exhaust_lobe_center + 2 * rot) // 3\n        .add_lobe(rot360 - exhaust_lobe_center + 8 * rot) // 5\n        .add_lobe(rot360 - exhaust_lobe_center + 4 * rot) // 7\n        .add_lobe(rot360 - exhaust_lobe_center + 6 * rot) // 9\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center + 0 * rot) // 1\n        .add_lobe(rot360 + intake_lobe_center + 2 * rot) // 3\n        .add_lobe(rot360 + intake_lobe_center + 8 * rot) // 5\n        .add_lobe(rot360 + intake_lobe_center + 4 * rot) // 7\n        .add_lobe(rot360 + intake_lobe_center + 6 * rot) // 9\n\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + 1 * rot) // 2\n        .add_lobe(rot360 - exhaust_lobe_center + 3 * rot) // 4\n        .add_lobe(rot360 - exhaust_lobe_center + 9 * rot) // 6\n        .add_lobe(rot360 - exhaust_lobe_center + 5 * rot) // 8\n        .add_lobe(rot360 - exhaust_lobe_center + 7 * rot) // 10\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + 1 * rot) // 2\n        .add_lobe(rot360 + intake_lobe_center + 3 * rot) // 4\n        .add_lobe(rot360 + intake_lobe_center + 9 * rot) // 6\n        .add_lobe(rot360 + intake_lobe_center + 5 * rot) // 8\n        .add_lobe(rot360 + intake_lobe_center + 7 * rot) // 10\n}\n\npublic node lr_gue_v10 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"1LR-GUE [V10]\",\n        starter_torque: 100 * units.lb_ft,\n        starter_speed: 200 * units.rpm,\n        redline: 9000 * units.rpm,\n        throttle_gamma: 2.0,\n        fuel: fuel(\n            max_turbulence_effect: 10.0,\n            max_dilution_effect: 20.0,\n            burning_efficiency_randomness: 0.25,\n            max_burning_efficiency: 1.0\n        ),\n        hf_gain: 0.01,\n        noise: 1.0,\n        jitter: 0.1,\n        simulation_frequency: 6500\n    )\n\n    wires wires()\n\n    label stroke(79 * units.mm)\n    label bore(88 * units.mm)\n    label rod_length(130 * units.mm)\n    label rod_mass(50 * units.g)\n    label compression_height(1.0 * units.inch)\n    label crank_mass(40 * units.lb)\n    label flywheel_mass(30 * units.lb)\n    label flywheel_radius(6.5 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 1 * units.kg, radius: 1.0 * units.cm)\n    )\n\n    label v_angle(72 * units.deg)\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 0.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: 90 * units.deg + (v_angle / 2.0)\n    )\n\n    // 72 degrees\n    rod_journal rj0(angle: 0 * v_angle)\n    rod_journal rj1(angle: 2 * v_angle)\n    rod_journal rj2(angle: 3 * v_angle)\n    rod_journal rj3(angle: 4 * v_angle)\n    rod_journal rj4(angle: 1 * v_angle)\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n        .add_rod_journal(rj4)\n\n    piston_parameters piston_params(\n        mass: (100) * units.g, // 414 - piston mass, 152 - pin weight\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: rod_mass,\n        moment_of_inertia: rod_moment_of_inertia(\n            mass: rod_mass,\n            length: rod_length\n        ),\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 1.325 * units.L,\n        plenum_cross_section_area: 20.0 * units.cm2,\n        intake_flow_rate: k_carb(1000.0),\n        runner_flow_rate: k_carb(200.0),\n        runner_length: 4.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.998,\n        velocity_decay: 0.5\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(2000.0),\n        primary_tube_length: 50.0 * units.inch,\n        primary_flow_rate: k_carb(1000.0),\n        velocity_decay: 1.0\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 2.0,\n        length: 100 * units.inch,\n        impulse_response: ir_lib.mild_exhaust_0_reverb\n    )\n    exhaust_system exhaust1(\n        es_params,\n        audio_volume: 2.0,\n        length: 100.5 * units.inch,\n        impulse_response: ir_lib.mild_exhaust_0_reverb\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    cylinder_bank b0(bank_params, angle: v_angle / 2.0)\n    cylinder_bank b1(bank_params, angle: -v_angle / 2.0)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire1,\n            sound_attenuation: 0.8,\n            primary_length: 5 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire3,\n            sound_attenuation: 1.0,\n            primary_length: 4 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire5,\n            sound_attenuation: 1.1,\n            primary_length: 3 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire7,\n            sound_attenuation: 0.9,\n            primary_length: 2 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj4,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire9,\n            sound_attenuation: 0.7,\n            primary_length: 1 * units.cm\n        )\n        .set_cylinder_head(\n            v10_72_head(\n                intake_camshaft: camshaft.intake_cam_0,\n                exhaust_camshaft: camshaft.exhaust_cam_0,\n                flip_display: true,\n                flow_attenuation: 1.5)\n        )\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire2,\n            sound_attenuation: 0.7,\n            primary_length: 5 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire4,\n            sound_attenuation: 0.8,\n            primary_length: 4 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire6,\n            primary_length: 3 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire8,\n            sound_attenuation: 1.1,\n            primary_length: 2 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj4,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire10,\n            sound_attenuation: 0.7,\n            primary_length: 1 * units.cm\n        )\n        .set_cylinder_head(\n            v10_72_head(\n                intake_camshaft: camshaft.intake_cam_1,\n                exhaust_camshaft: camshaft.exhaust_cam_1,\n                flow_attenuation: 1.5)\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 230 * units.deg,\n        gamma: 1.1,\n        lift: 15.95 * units.mm,\n        steps: 100\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 230 * units.deg,\n        gamma: 1.1,\n        lift: 15.95 * units.mm,\n        steps: 100\n    )\n\n    v10_72_camshaft camshaft(\n        lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 90 * units.deg,\n        exhaust_lobe_center: 112 * units.deg,\n        base_radius: 0.9 * units.inch\n    )\n\n    function timing_curve(4000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(4000 * units.rpm, 40 * units.deg)\n        .add_sample(8000 * units.rpm, 40 * units.deg)\n        .add_sample(12000 * units.rpm, 40 * units.deg)\n        .add_sample(14000 * units.rpm, 40 * units.deg)\n        .add_sample(18000 * units.rpm, 40 * units.deg)\n\n    ignition_module ignition_module(\n        timing_curve: timing_curve,\n        rev_limit: 9500 * units.rpm,\n        limiter_duration: 0.1)\n    ignition_module\n            .connect_wire(wires.wire1,  0 * 72 * units.deg)\n            .connect_wire(wires.wire2,  1 * 72 * units.deg)\n            .connect_wire(wires.wire3,  2 * 72 * units.deg)\n            .connect_wire(wires.wire4,  3 * 72 * units.deg)\n            .connect_wire(wires.wire7,  4 * 72 * units.deg)\n            .connect_wire(wires.wire8,  5 * 72 * units.deg)\n            .connect_wire(wires.wire9,  6 * 72 * units.deg)\n            .connect_wire(wires.wire10, 7 * 72 * units.deg)\n            .connect_wire(wires.wire5,  8 * 72 * units.deg)\n            .connect_wire(wires.wire6,  9 * 72 * units.deg)\n\n    engine.add_ignition_module(ignition_module)\n}\n\nprivate node lfa_vehicle {\n    alias output __out:\n        vehicle(\n            mass: 1614 * units.kg,\n            drag_coefficient: 0.3,\n            cross_sectional_area: (72 * units.inch) * (50 * units.inch),\n            diff_ratio: 3.42,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 200 * units.N\n        );\n}\n\nprivate node lfa_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 500 * units.lb_ft\n        )\n        .add_gear(3.23)\n        .add_gear(2.19)\n        .add_gear(1.61)\n        .add_gear(1.23)\n        .add_gear(0.97)\n        .add_gear(0.8);\n}\n\npublic node main {\n    set_engine(lr_gue_v10())\n    set_vehicle(lfa_vehicle())\n    set_transmission(lfa_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-2/11_merlin_v12.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\nlabel cycle(2 * 360 * units.deg)\n\nprivate node wires {\n    output wire1a: ignition_wire();\n    output wire2a: ignition_wire();\n    output wire3a: ignition_wire();\n    output wire4a: ignition_wire();\n    output wire5a: ignition_wire();\n    output wire6a: ignition_wire();\n    output wire1b: ignition_wire();\n    output wire2b: ignition_wire();\n    output wire3b: ignition_wire();\n    output wire4b: ignition_wire();\n    output wire5b: ignition_wire();\n    output wire6b: ignition_wire();\n}\n\nprivate node v12_60_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 450 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 2.0 * units.inch * 2.0 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 5.0 * units.inch * 3.0 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 58 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 103 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 156 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 214 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 249 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 268 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 281 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 37 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 72 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 113 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 196 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 222 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 245 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 246 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: standard_valvetrain(\n            intake_camshaft: intake_camshaft,\n            exhaust_camshaft: exhaust_camshaft\n        ),\n        flip_display: flip_display\n    )\n}\n\nprivate node v12_60_camshaft {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;  \n    input advance: 0 * units.deg; \n    input base_radius: 0.5 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output exhaust_cam_0: _exhaust_cam_0;\n\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot180(180 * units.deg)\n    label rot360(360 * units.deg)\n\n    // 1a 6b 4a 3b 2a 5b 6a 1b 3a 4b 5a 2b\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center + 0 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 240 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 480 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 120 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 600 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 360 * units.deg)\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center + 0 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 240 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 480 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 120 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 600 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 360 * units.deg)\n\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + (360 + 60) * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + (600 + 60) * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + (120 + 60) * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + (480 + 60) * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + (240 + 60) * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + (0 + 60) * units.deg)\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + (360 + 60) * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + (600 + 60) * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + (120 + 60) * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + (480 + 60) * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + (240 + 60) * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + (0 + 60) * units.deg)\n}\n\npublic node merlin_v12 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Merlin V-1650-9 [V12] (NA)\",\n        starter_torque: 190 * units.lb_ft,\n        starter_speed: 200 * units.rpm,\n        redline: 3000 * units.rpm,\n        fuel: fuel(\n            max_turbulence_effect: 10.0,\n            max_dilution_effect: 5.0,\n            burning_efficiency_randomness: 0.1,\n            max_burning_efficiency: 1.0\n        ),\n        throttle_gamma: 2.0,\n        simulation_frequency: 7000,\n        hf_gain: 0.004,\n        noise: 0.35,\n        jitter: 0.229\n    )\n\n    wires wires()\n\n    label stroke(6 * units.inch)\n    label bore(5.4 * units.inch)\n    label rod_length(14 * units.inch)\n    label rod_mass(2000 * units.g)\n    label compression_height(1.0 * units.inch)\n    label crank_mass(400 * units.lb)\n    label flywheel_mass(200 * units.lb)\n    label flywheel_radius(12 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 1 * units.kg, radius: 1.0 * units.cm)\n    )\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 50.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: (90 + 30) * units.deg\n    )\n\n    rod_journal rj0(angle: 0 * units.deg)\n    rod_journal rj1(angle: 240 * units.deg)\n    rod_journal rj2(angle: 120 * units.deg)\n    rod_journal rj3(angle: 120 * units.deg)\n    rod_journal rj4(angle: 240 * units.deg)\n    rod_journal rj5(angle: 0 * units.deg)\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n        .add_rod_journal(rj4)\n        .add_rod_journal(rj5)\n\n    piston_parameters piston_params(\n        mass: (1000) * units.g,\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: rod_mass,\n        moment_of_inertia: rod_moment_of_inertia(\n            mass: rod_mass,\n            length: rod_length\n        ),\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 1.325 * units.L,\n        plenum_cross_section_area: 20.0 * units.cm2,\n        intake_flow_rate: k_carb(1400.0),\n        runner_flow_rate: k_carb(200.0),\n        runner_length: 16.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.99,\n        velocity_decay: 0.5\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(2000.0),\n        primary_tube_length: 50.0 * units.inch,\n        primary_flow_rate: k_carb(400.0),\n        velocity_decay: 1.0\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        length: 30 * units.inch,\n        audio_volume: 1.0 * 0.5,\n        impulse_response: ir_lib.minimal_muffling_01\n    )\n    exhaust_system exhaust1(\n        es_params,\n        length: 70 * units.inch,\n        audio_volume: 1.0 * 0.5,\n        impulse_response: ir_lib.minimal_muffling_01\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    label spacing(6 * units.inch)\n\n    cylinder_bank b0(bank_params, angle: (60 / 2.0) * units.deg)\n    cylinder_bank b1(bank_params, angle: -(60 / 2.0) * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.7)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire1a,\n            sound_attenuation: 0.9,\n            primary_length: spacing * 6\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2a,\n            sound_attenuation: 1.0,\n            primary_length: spacing * 5\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.4)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire3a,\n            sound_attenuation: 1.5,\n            primary_length: spacing * 4\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.3)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4a,\n            sound_attenuation: 0.9,\n            primary_length: spacing * 3\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj4,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire5a,\n            sound_attenuation: 0.8,\n            primary_length: spacing * 2\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj5,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire6a,\n            sound_attenuation: 1.0,\n            primary_length: spacing * 1\n        )\n        .set_cylinder_head(\n            v12_60_head(\n                intake_camshaft: camshaft.intake_cam_0,\n                exhaust_camshaft: camshaft.exhaust_cam_0,\n                flip_display: true,\n                flow_attenuation: 1.0)\n        )\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.5)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1b,\n            sound_attenuation: 0.9,\n            primary_length: spacing * 6\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire2b,\n            sound_attenuation: 1.1,\n            primary_length: spacing * 5\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3b,\n            primary_length: spacing * 4\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.3)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire4b,\n            sound_attenuation: 1.1,\n            primary_length: spacing * 3\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj4,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire5b,\n            sound_attenuation: 0.7,\n            primary_length: spacing * 2\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj5,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire6b,\n            primary_length: spacing * 1\n        )\n        .set_cylinder_head(\n            v12_60_head(\n                intake_camshaft: camshaft.intake_cam_1,\n                exhaust_camshaft: camshaft.exhaust_cam_1,\n                flow_attenuation: 1.0)\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 242 * units.deg,\n        gamma: 0.8,\n        lift: 15.95 * units.mm,\n        steps: 100\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 246 * units.deg,\n        gamma: 0.8,\n        lift: 590 * units.thou,\n        steps: 100\n    )\n\n    v12_60_camshaft camshaft(\n        lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 100.5 * units.deg,\n        exhaust_lobe_center: 120 * units.deg,\n        base_radius: 1.0 * units.inch\n    )\n\n    function timing_curve(4000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(4000 * units.rpm, 50 * units.deg)\n\n    ignition_module ignition_module(\n        timing_curve: timing_curve,\n        rev_limit: 3500 * units.rpm,\n        limiter_duration: 0.05)\n    ignition_module\n            .connect_wire(wires.wire1a, (0) * units.deg)\n            .connect_wire(wires.wire6b, (0 + 60) * units.deg)\n            .connect_wire(wires.wire4a, (120) * units.deg)\n            .connect_wire(wires.wire3b, (120 + 60) * units.deg)\n            .connect_wire(wires.wire2a, (240) * units.deg)\n            .connect_wire(wires.wire5b, (240 + 60) * units.deg)\n            .connect_wire(wires.wire6a, (360) * units.deg)\n            .connect_wire(wires.wire1b, (360 + 60) * units.deg)\n            .connect_wire(wires.wire3a, (480) * units.deg)\n            .connect_wire(wires.wire4b, (480 + 60) * units.deg)\n            .connect_wire(wires.wire5a, (600) * units.deg)\n            .connect_wire(wires.wire2b, (600 + 60) * units.deg)\n\n    engine.add_ignition_module(ignition_module)\n}\n\nlabel car_mass(2700 * units.lb)\nprivate node random_car {\n    alias output __out:\n        vehicle(\n            mass: car_mass,\n            drag_coefficient: 0.3,\n            cross_sectional_area: (72 * units.inch) * (56 * units.inch),\n            diff_ratio: 3.9,\n            tire_radius: 10 * units.inch,\n            rolling_resistance: 10000\n        );\n}\n\nprivate node random_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 2000 * units.lb_ft\n        )\n        .add_gear(0.01);\n}\n\npublic node main {\n    set_engine(merlin_v12())\n    set_vehicle(random_car())\n    set_transmission(random_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-2/12_ferrari_412_t2.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\nlabel cycle(2 * 360 * units.deg)\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n    output wire6: ignition_wire();\n    output wire7: ignition_wire();\n    output wire8: ignition_wire();\n    output wire9: ignition_wire();\n    output wire10: ignition_wire();\n    output wire11: ignition_wire();\n    output wire12: ignition_wire();\n}\n\nprivate node v12_75_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 1.5 * 25 * units.cc;\n    input intake_runner_volume: 149.6 * units.cc;\n    input intake_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch;\n    input exhaust_runner_volume: 50.0 * units.cc;\n    input exhaust_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 58 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 103 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 156 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 214 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 249 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 268 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 280 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 281 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 37 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 72 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 113 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 196 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 222 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 245 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 246 * flow_attenuation)\n\n    generic_cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        valvetrain: standard_valvetrain(\n            intake_camshaft: intake_camshaft,\n            exhaust_camshaft: exhaust_camshaft\n        ),\n        flip_display: flip_display\n    )\n}\n\nprivate node v12_75_camshaft {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;  \n    input advance: 0 * units.deg; \n    input base_radius: 0.5 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output exhaust_cam_0: _exhaust_cam_0;\n\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot180(180 * units.deg)\n    label rot360(360 * units.deg)\n\n    // 1 12 5 8 3 10 6 7 2 11 4 9\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center + 0 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 480 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 240 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 600 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 120 * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + 360 * units.deg)\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center + 0 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 480 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 240 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 600 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 120 * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + 360 * units.deg)\n\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + (0 + 75) * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + (480 + 75) * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + (240 + 75) * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + (600 + 75) * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + (120 + 75) * units.deg)\n        .add_lobe(rot360 - exhaust_lobe_center + (360 + 75) * units.deg)\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + (0 + 75) * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + (480 + 75) * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + (240 + 75) * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + (600 + 75) * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + (120 + 75) * units.deg)\n        .add_lobe(rot360 + intake_lobe_center + (360 + 75) * units.deg)\n}\n\nprivate node turbulence_to_flame_speed_ratio {\n    alias output __out:\n        function(5.0)\n            .add_sample(0.0, 2.0 * 3.0)\n            .add_sample(5.0, 2.0 * 1.5 * 5.0)\n            .add_sample(10.0, 2.5 * 1.5 * 10.0)\n            .add_sample(15.0, 3.0 * 1.5 * 15.0)\n            .add_sample(20.0, 3.0 * 1.5 * 20.0)\n            .add_sample(25.0, 3.0 * 1.5 * 25.0)\n            .add_sample(30.0, 3.0 * 1.5 * 30.0)\n            .add_sample(35.0, 3.0 * 1.5 * 35.0)\n            .add_sample(40.0, 3.0 * 1.5 * 40.0)\n            .add_sample(45.0, 3.0 * 1.5 * 45.0);\n}\n\npublic node ferrari_412_t2_v12 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Ferrari 412 T2 [V12]\",\n        starter_torque: 70 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 18000 * units.rpm,\n        throttle_gamma: 2.0,\n        fuel: fuel(\n            max_turbulence_effect: 10.0,\n            max_dilution_effect: 5.0,\n            burning_efficiency_randomness: 1.0,\n            max_burning_efficiency: 1.0,\n            turbulence_to_flame_speed_ratio: turbulence_to_flame_speed_ratio()\n        ),\n        hf_gain: 0.01,\n        noise: 1.0,\n        jitter: 0.1,\n        simulation_frequency: 5000\n    )\n\n    wires wires()\n\n    label stroke(43 * units.mm)\n    label bore(86 * units.mm)\n    label rod_length(120 * units.mm)\n    label rod_mass(50 * units.g)\n    label compression_height(1.0 * units.inch)\n    label crank_mass(20 * units.lb)\n    label flywheel_mass(10 * units.lb)\n    label flywheel_radius(5 * units.inch)\n\n    label crank_moment(\n        disk_moment_of_inertia(mass: crank_mass, radius: stroke)\n    )\n    label flywheel_moment(\n        disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius)\n    )\n    label other_moment( // Moment from cams, pulleys, etc [estimated]\n        disk_moment_of_inertia(mass: 1 * units.kg, radius: 1.0 * units.cm)\n    )\n\n    crankshaft c0(\n        throw: stroke / 2,\n        flywheel_mass: flywheel_mass,\n        mass: crank_mass,\n        friction_torque: 1.0 * units.lb_ft,\n        moment_of_inertia:\n            crank_moment + flywheel_moment + other_moment,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: (90 + (75 / 2.0)) * units.deg\n    )\n\n    // 1 12 5 8 3 10 6 7 2 11 4 9\n    rod_journal rj0(angle: 0 * units.deg)\n    rod_journal rj1(angle: 120 * units.deg)\n    rod_journal rj2(angle: 240 * units.deg)\n    rod_journal rj3(angle: 240 * units.deg)\n    rod_journal rj4(angle: 120 * units.deg)\n    rod_journal rj5(angle: 0 * units.deg)\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n        .add_rod_journal(rj4)\n        .add_rod_journal(rj5)\n\n    piston_parameters piston_params(\n        mass: (50) * units.g, // 414 - piston mass, 152 - pin weight\n        compression_height: compression_height,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: rod_mass,\n        moment_of_inertia: rod_moment_of_inertia(\n            mass: rod_mass,\n            length: rod_length\n        ),\n        center_of_mass: 0.0,\n        length: rod_length\n    )\n\n    intake intake(\n        plenum_volume: 1.325 * units.L,\n        plenum_cross_section_area: 20.0 * units.cm2,\n        intake_flow_rate: k_carb(1400.0),\n        runner_flow_rate: k_carb(200.0),\n        runner_length: 4.0 * units.inch,\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.992,\n        velocity_decay: 0.5\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(2000.0),\n        primary_tube_length: 20.0 * units.inch,\n        primary_flow_rate: k_carb(200.0),\n        velocity_decay: 0.5\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 1.0 * 0.004,\n        length: 20 * units.inch,\n        impulse_response: ir_lib.minimal_muffling_01\n    )\n    exhaust_system exhaust1(\n        es_params,\n        audio_volume: 1.0 * 0.004,\n        length: 56 * units.inch,\n        impulse_response: ir_lib.minimal_muffling_01\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: bore,\n        deck_height: stroke / 2 + rod_length + compression_height\n    )\n\n    label spacing(0.1)\n\n    cylinder_bank b0(bank_params, angle: (75 / 2.0) * units.deg)\n    cylinder_bank b1(bank_params, angle: -(75 / 2.0) * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire1,\n            sound_attenuation: 0.5,\n            primary_length: spacing * 0.5 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2,\n            sound_attenuation: 1.0,\n            primary_length: spacing * 0.0 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire3,\n            sound_attenuation: 0.75,\n            primary_length: spacing * 0.2 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4,\n            sound_attenuation: 0.9,\n            primary_length: spacing * 1.5 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj4,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire5,\n            sound_attenuation: 0.7,\n            primary_length: spacing * 2.5 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj5,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire6,\n            sound_attenuation: 1.0,\n            primary_length: spacing * 0.5 * units.cm\n        )\n        .set_cylinder_head(\n            v12_75_head(\n                intake_camshaft: camshaft.intake_cam_0,\n                exhaust_camshaft: camshaft.exhaust_cam_0,\n                flip_display: true,\n                flow_attenuation: 1.0)\n        )\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire12,\n            sound_attenuation: 0.5,\n            primary_length: spacing * 0.5 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire11,\n            sound_attenuation: 0.3,\n            primary_length: spacing * 0.25 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire10,\n            primary_length: spacing * 3.5 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire9,\n            sound_attenuation: 1.2,\n            primary_length: spacing * 1.5 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj4,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire8,\n            sound_attenuation: 0.7,\n            primary_length: spacing * 0.5 * units.cm\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.0)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj5,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire7,\n            sound_attenuation: 1.2,\n            primary_length: spacing * 1.5 * units.cm\n        )\n        .set_cylinder_head(\n            v12_75_head(\n                intake_camshaft: camshaft.intake_cam_1,\n                exhaust_camshaft: camshaft.exhaust_cam_1,\n                flow_attenuation: 1.0)\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe intake_lobe(\n        duration_at_50_thou: 242 * units.deg,\n        gamma: 0.8,\n        lift: 15.95 * units.mm,\n        steps: 512\n    )\n\n    harmonic_cam_lobe exhaust_lobe(\n        duration_at_50_thou: 246 * units.deg,\n        gamma: 0.8,\n        lift: 15.95 * units.mm,\n        steps: 512\n    )\n\n    v12_75_camshaft camshaft(\n        lobe_profile: \"N/A\",\n\n        intake_lobe_profile: intake_lobe,\n        exhaust_lobe_profile: exhaust_lobe,\n        intake_lobe_center: 90 * units.deg,\n        exhaust_lobe_center: 112 * units.deg,\n        base_radius: 1.0 * units.inch\n    )\n\n    function timing_curve(4000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(4000 * units.rpm, 40 * units.deg)\n        .add_sample(8000 * units.rpm, 40 * units.deg)\n        .add_sample(12000 * units.rpm, 40 * units.deg)\n        .add_sample(14000 * units.rpm, 40 * units.deg)\n        .add_sample(18000 * units.rpm, 40 * units.deg)\n\n    ignition_module ignition_module(\n        timing_curve: timing_curve,\n        rev_limit: 18500 * units.rpm,\n        limiter_duration: 0.1)\n    ignition_module\n            .connect_wire(wires.wire1,  (0) * units.deg)\n            .connect_wire(wires.wire12, (0 + 75) * units.deg)\n            .connect_wire(wires.wire5,  (120) * units.deg)\n            .connect_wire(wires.wire8,  (120 + 75) * units.deg)\n            .connect_wire(wires.wire3,  (240) * units.deg)\n            .connect_wire(wires.wire10, (240 + 75) * units.deg)\n            .connect_wire(wires.wire6,  (360) * units.deg)\n            .connect_wire(wires.wire7,  (360 + 75) * units.deg)\n            .connect_wire(wires.wire2,  (480) * units.deg)\n            .connect_wire(wires.wire11, (480 + 75) * units.deg)\n            .connect_wire(wires.wire4,  (600) * units.deg)\n            .connect_wire(wires.wire9,  (600 + 75) * units.deg)\n\n    engine.add_ignition_module(ignition_module)\n}\n\nprivate node f1_vehicle {\n    alias output __out:\n        vehicle(\n            mass: 798 * units.kg,\n            drag_coefficient: 0.9,\n            cross_sectional_area: (72 * units.inch) * (36 * units.inch),\n            diff_ratio: 4.10,\n            tire_radius: 9 * units.inch,\n            rolling_resistance: 200 * units.N\n        );\n}\n\nprivate node f1_transmission {\n    alias output __out:\n        transmission(\n            max_clutch_torque: 1000 * units.lb_ft\n        )\n        .add_gear(2.8)\n        .add_gear(2.29)\n        .add_gear(1.93)\n        .add_gear(1.583)\n        .add_gear(1.375)\n        .add_gear(1.19);\n}\n\npublic node main {\n    set_engine(ferrari_412_t2_v12())\n    set_vehicle(f1_vehicle())\n    set_transmission(f1_transmission())\n}\n"
  },
  {
    "path": "assets/engines/atg-video-2/README.md",
    "content": "## Engines from *Simulating an F1 V12, cross-plane and flat-plane V8s, unequal length headers and more*\n\nThis folder contains all the engines from [*Simulating an F1 V12, cross-plane and flat-plane V8s, unequal length headers and more*](https://youtu.be/NBBwva3NZ6o). Each file contains a node called `main` and it can be used in `main.mr` as follows.\n\n```\nimport \"engines/atg-video-2/<insert filename>\"\n\nmain()\n```\n\nThe following engines are included:\n1. Subaru EJ25 with equal length headers\n2. Subaru EJ25 with unequal length headers\n3. Toyota 2JZ\n4. 60 degree V6\n5. Odd-fire 90 degree V6\n6. Even-fire 90 degree V6\n7. GM LS V8\n8. Ferrari F136 V8\n9. Radial-9\n10. Toyota 1LR-GUE (Lexus LFA)\n11. Merlin V12\n12. Ferrari Tipo 044/1 (Formula 1 V12)\n"
  },
  {
    "path": "assets/engines/atg-video-2/radial.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nlabel cycle(2 * 360 * units.deg)\n\npublic node radial_head {\n    input offset;\n    input lobe_profile;\n    input chamber_volume: 290 * units.cc;\n    alias output __head:\n        generic_small_engine_head(\n            chamber_volume: chamber_volume,\n            intake_camshaft: camshaft.intake_cam,\n            exhaust_camshaft: camshaft.exhaust_cam,\n            flow_attenuation: 2.0,\n            intake_runner_cross_section_area: 20.0 * units.cm2,\n            exhaust_runner_cross_section_area: 20.0 * units.cm2\n        );\n\n    radial_camshaft camshaft(\n        lobe_profile: lobe_profile,\n        offset: offset\n    )\n}\n\npublic node radial_camshaft {\n    input lobe_profile;\n    input offset;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;  \n    input advance: 0 * units.deg;\n    input base_radius: 1.0 * units.inch;\n\n    output intake_cam: _intake_cam;\n    output exhaust_cam: _exhaust_cam;\n\n    camshaft_parameters params (\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot180(180 * units.deg)\n    label rot360(360 * units.deg)\n\n    _exhaust_cam\n        .add_lobe(rot360 - exhaust_lobe_center + offset * cycle)\n    _intake_cam\n        .add_lobe(rot360 + intake_lobe_center + offset * cycle)\n}\n"
  },
  {
    "path": "assets/engines/audi/i5.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n}\n\nlabel cycle(2 * 360 * units.deg)\npublic node vemsign {\n    input wires;\n    input timing_curve;\n    input rev_limit: 7500 * units.rpm;\n    alias output __out:\n        ignition_module(timing_curve: timing_curve, rev_limit: rev_limit)\n            .connect_wire(wires.wire1, (0.0 / 5.0) * cycle)\n            .connect_wire(wires.wire2, (1.0 / 5.0) * cycle)\n            .connect_wire(wires.wire4, (2.0 / 5.0) * cycle)\n            .connect_wire(wires.wire5, (3.0 / 5.0) * cycle)\n            .connect_wire(wires.wire3, (4.0 / 5.0) * cycle);\n}\n\npublic node i5_camshaft_builder {\n    input lobe_profile: comp_cams_magnum_11_450_8_lobe_profile();\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 110.0 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;\n    input advance: 0.0 * units.deg;\n    input base_radius: 0.75 * units.inch;\n\n    output intake_cam: _intake_cam;\n    output exhaust_cam: _exhaust_cam;\n\n    camshaft_parameters params(\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot(2 * (360 / 5.0) * units.deg)\n    label rot360(360 * units.deg)\n\n    _exhaust_cam\n        .add_lobe(rot360 - exhaust_lobe_center)\n        .add_lobe((rot360 - exhaust_lobe_center) + 1 * rot)\n        .add_lobe((rot360 - exhaust_lobe_center) + 4 * rot)\n        .add_lobe((rot360 - exhaust_lobe_center) + 2 * rot)\n        .add_lobe((rot360 - exhaust_lobe_center) + 3 * rot)\n\n    _intake_cam\n        .add_lobe(rot360 + intake_lobe_center)\n        .add_lobe(rot360 + intake_lobe_center + 1 * rot)\n        .add_lobe(rot360 + intake_lobe_center + 4 * rot)\n        .add_lobe(rot360 + intake_lobe_center + 2 * rot)\n        .add_lobe(rot360 + intake_lobe_center + 3 * rot)\n}\n\npublic node audi_i5_2_2L {\n    alias output __out: engine;\n\n    wires wires()\n\n    engine engine(\n        name: \"Audi 2.2 inline 5\",\n        starter_torque: 200 * units.lb_ft,\n        redline: 7500 * units.rpm,\n        fuel: fuel(\n            max_turbulence_effect: 4.0,\n            burning_efficiency_randomness: 0.2,\n            max_burning_efficiency: 0.85)\n    )\n\n    crankshaft c0(\n        throw: 0.5 * 86.4 * units.mm,\n        flywheel_mass: 20 * units.lb,\n        mass: 40 * units.lb,\n        friction_torque: 10.0 * units.lb_ft,\n        moment_of_inertia: 0.22986844776863666 * 0.9,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: constants.pi / 2\n    )\n\n    rod_journal rj0(angle: (0.0 / 5.0) * 360 * units.deg)\n\trod_journal rj1(angle: (2.0 / 5.0) * 360 * units.deg)\n\trod_journal rj2(angle: (3.0 / 5.0) * 360 * units.deg)\n\trod_journal rj3(angle: (4.0 / 5.0) * 360 * units.deg)\n    rod_journal rj4(angle: (1.0 / 5.0) * 360 * units.deg)\n\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n        .add_rod_journal(rj4)\n\n    piston_parameters piston_params(\n        mass: 400 * units.g,\n        blowby: 0,\n        compression_height: 32.8 * units.mm,\n        wrist_pin_position: 0 * units.mm,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: 300.0 * units.g,\n        moment_of_inertia: 0.0015884918028487504,\n        center_of_mass: 0.0,\n        length: (220 - 0.5 * 86.4 - 32.8) * units.mm\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: 81 * units.mm,\n        deck_height: 220 * units.mm\n    )\n\n    intake intake(\n        plenum_volume: 5.0 * units.L,\n        plenum_cross_section_area: 10.0 * units.cm2,\n        intake_flow_rate: k_carb(500.0),\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.995\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(200.0),\n        primary_tube_length: 10.0 * units.inch,\n        primary_flow_rate: k_carb(100.0),\n        velocity_decay: 0.5, //0.5\n        volume: 50.0 * units.L\n    )\n\n    exhaust_system exhaust0(es_params, audio_volume: 1.0, impulse_response: ir_lib.default_0)\n    exhaust_system exhaust1(es_params, audio_volume: 0.8, impulse_response: ir_lib.default_0)\n\n    cylinder_bank b0(bank_params, angle: 0)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.6)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.6)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.4)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.4)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj4,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire5\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n\n    engine.add_crankshaft(c0)\n\n    i5_camshaft_builder camshaft()\n\n    b0.set_cylinder_head (\n        generic_small_engine_head(\n            chamber_volume: 50 * units.cc,\n            intake_camshaft: camshaft.intake_cam,\n            exhaust_camshaft: camshaft.exhaust_cam\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(1000 * units.rpm, 12 * units.deg)\n        .add_sample(2000 * units.rpm, 20 * units.deg)\n        .add_sample(3000 * units.rpm, 26 * units.deg)\n        .add_sample(4000 * units.rpm, 30 * units.deg)\n        .add_sample(5000 * units.rpm, 34 * units.deg)\n        .add_sample(6000 * units.rpm, 38 * units.deg)\n        .add_sample(7000 * units.rpm, 38 * units.deg)\n\n    engine.add_ignition_module(\n        vemsign(\n            wires: wires,\n            timing_curve: timing_curve,\n            rev_limit: 7500 * units.rpm\n        )\n    )\n}"
  },
  {
    "path": "assets/engines/bmw/M52B28.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\n\nlabel cycle(2 * 360 * units.deg)\n\npublic node bmw_distributor {\n    input wires;\n    input timing_curve;\n    input rev_limit: 8500 * units.rpm;\n    alias output __out:\n        ignition_module(timing_curve: timing_curve, rev_limit: rev_limit)\n            .connect_wire(wires.wire1, (0.0/6.0) * cycle)\n            .connect_wire(wires.wire5, (1.0/6.0) * cycle)\n\t\t\t.connect_wire(wires.wire3, (2.0/6.0) * cycle)\n\t\t\t.connect_wire(wires.wire6, (3.0/6.0) * cycle)\n\t\t\t.connect_wire(wires.wire2, (4.0/6.0) * cycle)\n\t\t\t.connect_wire(wires.wire4, (5.0/6.0) * cycle);\n}\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n    output wire6: ignition_wire();\n}\n\nprivate node add_sym_sample {\n    input angle;\n    input lift;\n    input this;\n    alias output __out: this;\n\n    this.add_sample(angle * units.deg, lift * units.thou)\n    this.add_sample(-angle * units.deg, lift * units.thou)\n}\n\npublic node m52b28_lobe_profile_int {\n    alias output __out:\n        harmonic_cam_lobe(\n            duration_at_50_thou: 210 * units.deg,\n            gamma: 0.8,\n            lift: 9.0 * units.mm,\n            steps: 100\n        );\n}\n\npublic node m52b28_lobe_profile_exh {\n    alias output __out:\n        harmonic_cam_lobe(\n            duration_at_50_thou: 210 * units.deg,\n            gamma: 0.8,\n            lift: 9.0 * units.mm,\n            steps: 100\n        );\n}\n\npublic node bmw_camshaft_builder {\n    input lobe_profile: m52b28_lobe_profile_int();\n\tinput ex_lobe_profile: m52b28_lobe_profile_exh();\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: ex_lobe_profile;\n    input lobe_separation: 110.0 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: 105.0 * units.deg;\n    input advance: 0.0 * units.deg;\n    input base_radius: 0.6 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output exhaust_cam_0: _exhaust_cam_0;\n\n    camshaft_parameters params(\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n\n\n    label rot60(60 * units.deg)\n    label rot90(90 * units.deg)\n    label rot120(120 * units.deg)\n    label rot180(180 * units.deg)\n    label rot360(360 * units.deg)\n\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center)\n\t\t.add_lobe(rot360 + intake_lobe_center + 4 * rot120)\n\t\t.add_lobe(rot360 + intake_lobe_center + 2 * rot120)\n\t\t.add_lobe(rot360 + intake_lobe_center + 5 * rot120)\n\t\t.add_lobe(rot360 + intake_lobe_center + 1 * rot120)\n\t\t.add_lobe(rot360 + intake_lobe_center + 3 * rot120)\n\n\t_exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center)\n        .add_lobe(rot360 - exhaust_lobe_center + 4 * rot120)\n\t\t.add_lobe(rot360 - exhaust_lobe_center + 2 * rot120)\n\t\t.add_lobe(rot360 - exhaust_lobe_center + 5 * rot120)\n\t\t.add_lobe(rot360 - exhaust_lobe_center + 1 * rot120)\n\t\t.add_lobe(rot360 - exhaust_lobe_center + 3 * rot120)\n}\n\nprivate node add_flow_sample {\n    input lift;\n    input flow;\n    input this;\n    alias output __out: this;\n\n    this.add_sample(lift * units.mm, k_28inH2O(flow))\n}\n\npublic node bmw_m52b28_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 34.0 * units.cc;\n    input flip_display: false;\n\t\n\tinput flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    alias output __out: head;\n\n    function intake_flow(1 * units.mm)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(1 * lift_scale, 35 * flow_attenuation)\n        .add_flow_sample(2 * lift_scale, 60 * flow_attenuation)\n        .add_flow_sample(3 * lift_scale, 90 * flow_attenuation)\n        .add_flow_sample(4 * lift_scale, 125 * flow_attenuation)\n        .add_flow_sample(5 * lift_scale, 150 * flow_attenuation)\n        .add_flow_sample(6 * lift_scale, 175 * flow_attenuation)\n        .add_flow_sample(7 * lift_scale, 200 * flow_attenuation)\n        .add_flow_sample(8 * lift_scale, 215 * flow_attenuation)\n        .add_flow_sample(9 * lift_scale, 230 * flow_attenuation)\n        .add_flow_sample(10 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(11 * lift_scale, 235 * flow_attenuation)\n        .add_flow_sample(12 * lift_scale, 238 * flow_attenuation)\n\n    function exhaust_flow(1 * units.mm)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(1 * lift_scale, 35 * flow_attenuation)\n        .add_flow_sample(2 * lift_scale, 55 * flow_attenuation)\n        .add_flow_sample(3 * lift_scale, 85 * flow_attenuation)\n        .add_flow_sample(4 * lift_scale, 105 * flow_attenuation)\n        .add_flow_sample(5 * lift_scale, 120 * flow_attenuation)\n        .add_flow_sample(6 * lift_scale, 140 * flow_attenuation)\n        .add_flow_sample(7 * lift_scale, 150 * flow_attenuation)\n        .add_flow_sample(8 * lift_scale, 155 * flow_attenuation)\n        .add_flow_sample(9 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(10 * lift_scale, 165 * flow_attenuation)\n        .add_flow_sample(11 * lift_scale, 165 * flow_attenuation)\n        .add_flow_sample(12 * lift_scale, 165 * flow_attenuation)\n\n\t\t\n    cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: 100.0 * units.cc,\n        intake_runner_cross_section_area: 2 * 12.4087 * units.cm2,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        intake_camshaft: intake_camshaft,\n        exhaust_camshaft: exhaust_camshaft,\n        flip_display: flip_display\n    )\n}\n\npublic node M52B28 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"BMW M52B28\",\n        starter_torque: 150 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 7000 * units.rpm,\n        fuel: fuel(\n            max_turbulence_effect: 4.0\n        ),\n        throttle_gamma: 2.0\n    )\n\n    wires wires()\n\n    crankshaft c0(\n        throw: 84 * units.mm / 2,\n        flywheel_mass: 5.9 * units.kg,\n        mass: 5 * units.kg,\n        friction_torque: 10.0 * units.lb_ft,\n        moment_of_inertia: 0.22986844776863666 * 0.9,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: 120.0 * units.deg\n    )\n\n    rod_journal rj0(angle: 0.0)\n    rod_journal rj1(angle: 120*units.deg)\n    rod_journal rj2(angle: 240*units.deg)\n    rod_journal rj3(angle: 240*units.deg)\n    rod_journal rj4(angle: 120*units.deg)\n    rod_journal rj5(angle: 0*units.deg)\n\t\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n        .add_rod_journal(rj4)\n        .add_rod_journal(rj5)\n\n    piston_parameters piston_params(\n        mass: 280 * units.g,\n        //blowby: k_28inH2O(0.1),\n        compression_height: 31.82 * units.mm,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: 300.0 * units.g,\n        moment_of_inertia: 0.0015884918028487504,\n        center_of_mass: 0.0,\n        length: 135.0 * units.mm\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: 84 * units.mm,\n        deck_height: (210.0 + 1) * units.mm\n    )\n\n    performer_rpm_intake intake(\n        carburetor_cfm: 500.0,\n        idle_flow_rate_cfm: 0.1,\n        idle_throttle_plate_position: 0.994\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(1000.0),\n        primary_tube_length: 20.0 * units.inch,\n        primary_flow_rate: k_carb(200.0),\n        velocity_decay: 1.0, //0.5\n        volume: 50.0 * units.L\n    )\n\n    exhaust_system exhaust0(es_params, audio_volume: 0.5, impulse_response: ir_lib.default_0)\n    exhaust_system exhaust1(es_params, audio_volume: 1.0, impulse_response: ir_lib.default_0)\n \n    cylinder_bank b0(bank_params, angle: 0 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire1\n        )\n\t\t.add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire2\n        )\n\t\t.add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire3\n        )\n\t\t.add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire4\n        )\n\t\t.add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj4,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire5\n        )\n\t\t.add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj5,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire6\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe lobe(\n        duration_at_50_thou: 256 * units.deg,\n        gamma: 1.1,\n        lift: 10.2 * units.mm,\n        steps: 100\n    )\n\n    bmw_camshaft_builder camshaft(\n\t    lobe_profile: m52b28_lobe_profile_int(),\n\t\tex_lobe_profile: m52b28_lobe_profile_exh()\n\t)\n\n    b0.set_cylinder_head (\n        bmw_m52b28_head(\n            intake_camshaft: camshaft.intake_cam_0,\n            exhaust_camshaft: camshaft.exhaust_cam_0\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 10 * units.deg)\n        .add_sample(1000 * units.rpm, 10 * units.deg)\n        .add_sample(2000 * units.rpm, 30 * units.deg)\n        .add_sample(3000 * units.rpm, 30 * units.deg)\n        .add_sample(4000 * units.rpm, 30 * units.deg)\n        .add_sample(5000 * units.rpm, 30 * units.deg)\n        .add_sample(6000 * units.rpm, 30 * units.deg)\n        .add_sample(7000 * units.rpm, 30 * units.deg)\n\n    engine.add_ignition_module(\n        bmw_distributor(\n            wires: wires,\n            timing_curve: timing_curve,\n            rev_limit: 8000 * units.rpm\n        ))\n}\n"
  },
  {
    "path": "assets/engines/chevrolet/chev_truck_454.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n    output wire6: ignition_wire();\n    output wire7: ignition_wire();\n    output wire8: ignition_wire();\n}\n\npublic node chev_truck_454 {\n    alias output __out: engine;\n\n    wires wires()\n\n    engine engine(\n        name: \"Chev. 454 V8\",\n        starter_torque: 200 * units.lb_ft,\n        redline: 5500 * units.rpm,\n        fuel: fuel(\n            max_turbulence_effect: 3.0,\n            burning_efficiency_randomness: 0.5,\n            max_burning_efficiency: 0.85),\n        throttle_gamma: 1.5\n    )\n\n    crankshaft c0(\n        throw: 2.0 * units.inch,\n        flywheel_mass: 29 * 2 * units.lb,\n        mass: 75 * units.lb,\n        friction_torque: 10.0 * units.lb_ft,\n        moment_of_inertia: 0.22986844776863666 * 2,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: constants.pi / 4\n    )\n\n    rod_journal rj0(angle: 0.0)\n    rod_journal rj1(angle: -constants.pi / 2)\n    rod_journal rj2(angle: -3.0 * constants.pi / 2)\n    rod_journal rj3(angle: constants.pi)\n\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n\n    piston_parameters piston_params(\n        mass: 880 * units.g,\n        blowby: 0,\n        compression_height: 1.640 * units.inch,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: 785.0 * units.g,\n        moment_of_inertia: 0.0015884918028487504,\n        center_of_mass: 0.0,\n        length: 6.135 * units.inch\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: 4.25 * units.inch,\n        deck_height: 9.8 * units.inch\n    )\n\n    chevy_bbc_stock_intake intake(\n        carburetor_cfm: 650.0,\n        idle_flow_rate_cfm: 0.007,\n        idle_throttle_plate_position: 0.991\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(550.0),\n        primary_tube_length: 10.0 * units.inch,\n        primary_flow_rate: k_carb(100.0),\n        velocity_decay: 1.0, //0.5\n        length: 100.0 * units.inch\n    )\n\n    label distance(5.0 * units.inch)\n\n    exhaust_system exhaust0(\n        es_params,\n        length: (180 + 72.0) * units.inch,\n        audio_volume: 5.5,\n        impulse_response: ir_lib.default_0)\n    exhaust_system exhaust1(\n        es_params,\n        length: 180.0 * units.inch,\n        audio_volume: 5.5, \n        impulse_response: ir_lib.default_0)\n\n    cylinder_bank b0(bank_params, angle: -45 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1,\n            primary_length: distance * 4,\n            sound_attenuation: 0.9\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.6)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3,\n            primary_length: distance * 3,\n            sound_attenuation: 1.1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.6)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire5,\n            primary_length: distance * 2,\n            sound_attenuation: 0.8\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.4)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire7,\n            primary_length: distance * 1,\n            sound_attenuation: 0.85\n        )\n\n    cylinder_bank b1(bank_params, angle: 45.0 * units.deg)\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2,\n            primary_length: distance * 4,\n            sound_attenuation: 0.9\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4,\n            primary_length: distance * 3,\n            sound_attenuation: 1.1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.6)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire6,\n            primary_length: distance * 2,\n            sound_attenuation: 0.8\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.6)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire8,\n            primary_length: distance * 1,\n            sound_attenuation: 0.75\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    chevy_454_stock_camshaft camshaft()\n\n    b0.set_cylinder_head (\n        chevy_bbc_peanut_port_head(\n            intake_camshaft: camshaft.intake_cam_0,\n            exhaust_camshaft: camshaft.exhaust_cam_0\n        )\n    )\n    b1.set_cylinder_head (\n        chevy_bbc_peanut_port_head(\n            intake_camshaft: camshaft.intake_cam_1,\n            exhaust_camshaft: camshaft.exhaust_cam_1,\n            flip_display: true\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(1000 * units.rpm, 12 * units.deg)\n        .add_sample(2000 * units.rpm, 20 * units.deg)\n        .add_sample(3000 * units.rpm, 30 * units.deg)\n        .add_sample(4000 * units.rpm, 38 * units.deg)\n        .add_sample(5000 * units.rpm, 38 * units.deg)\n        .add_sample(6000 * units.rpm, 38 * units.deg)\n\n    engine.add_ignition_module(\n        chevy_bbc_distributor(\n            wires: wires,\n            timing_curve: timing_curve,\n            rev_limit: 7000 * units.rpm\n        )\n    )\n}\n"
  },
  {
    "path": "assets/engines/chevrolet/engine_03_for_e1.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n    output wire3: ignition_wire();\n    output wire4: ignition_wire();\n    output wire5: ignition_wire();\n    output wire6: ignition_wire();\n    output wire7: ignition_wire();\n    output wire8: ignition_wire();\n}\n\npublic node engine_03_for_e1 {\n    alias output __out: engine;\n\n    wires wires()\n\n    engine engine(\n        name: \"Chev. 454 V8\",\n        starter_torque: 200 * units.lb_ft,\n        redline: 5500 * units.rpm,\n        fuel: fuel(\n            max_turbulence_effect: 3.0,\n            burning_efficiency_randomness: 0.5,\n            max_burning_efficiency: 0.85),\n        throttle_gamma: 1.5\n    )\n\n    crankshaft c0(\n        throw: 2.0 * units.inch,\n        flywheel_mass: 29 * 2 * units.lb,\n        mass: 75 * units.lb,\n        friction_torque: 10.0 * units.lb_ft,\n        moment_of_inertia: 0.22986844776863666 * 2,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: constants.pi / 4\n    )\n\n    rod_journal rj0(angle: 0.0)\n    rod_journal rj1(angle: -constants.pi / 2)\n    rod_journal rj2(angle: -3.0 * constants.pi / 2)\n    rod_journal rj3(angle: constants.pi)\n\n    c0\n        .add_rod_journal(rj0)\n        .add_rod_journal(rj1)\n        .add_rod_journal(rj2)\n        .add_rod_journal(rj3)\n\n    piston_parameters piston_params(\n        mass: 880 * units.g,\n        blowby: 0,\n        compression_height: 1.640 * units.inch,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: 785.0 * units.g,\n        moment_of_inertia: 0.0015884918028487504,\n        center_of_mass: 0.0,\n        length: 6.135 * units.inch\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: 4.25 * units.inch,\n        deck_height: 9.8 * units.inch\n    )\n\n    chevy_bbc_stock_intake intake(\n        carburetor_cfm: 950.0,\n        idle_flow_rate_cfm: 0.007,\n        idle_throttle_plate_position: 0.995\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(550.0),\n        primary_tube_length: 10.0 * units.inch,\n        primary_flow_rate: k_carb(90.0),\n        velocity_decay: 1.0, //0.5\n        volume: 50.0 * units.L\n    )\n\n    exhaust_system exhaust0(es_params, audio_volume: 1.0, impulse_response: ir_lib.default_0)\n    exhaust_system exhaust1(es_params, audio_volume: 0.1, impulse_response: ir_lib.default_0)\n\n    cylinder_bank b0(bank_params, angle: -45 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.6)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire3\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.6)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire5\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.4)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire7\n        )\n\n    cylinder_bank b1(bank_params, angle: 45.0 * units.deg)\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire2\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.2)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj1,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire4\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.6)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj2,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire6\n        )\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.6)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj3,\n            intake: intake,\n            exhaust_system: exhaust1,\n            ignition_wire: wires.wire8\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    comp_cams_magnum_11_450_8 camshaft()\n\n    b0.set_cylinder_head (\n        chevy_bbc_peanut_port_head(\n            intake_camshaft: camshaft.intake_cam_0,\n            exhaust_camshaft: camshaft.exhaust_cam_0\n        )\n    )\n    b1.set_cylinder_head (\n        chevy_bbc_peanut_port_head(\n            intake_camshaft: camshaft.intake_cam_1,\n            exhaust_camshaft: camshaft.exhaust_cam_1,\n            flip_display: true\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 12 * units.deg)\n        .add_sample(1000 * units.rpm, 12 * units.deg)\n        .add_sample(2000 * units.rpm, 20 * units.deg)\n        .add_sample(3000 * units.rpm, 30 * units.deg)\n        .add_sample(4000 * units.rpm, 38 * units.deg)\n        .add_sample(5000 * units.rpm, 38 * units.deg)\n        .add_sample(6000 * units.rpm, 38 * units.deg)\n\n    engine.add_ignition_module(\n        chevy_bbc_distributor(\n            wires: wires,\n            timing_curve: timing_curve,\n            rev_limit: 7000 * units.rpm\n        )\n    )\n}\n"
  },
  {
    "path": "assets/engines/kohler/kohler_ch750.mr",
    "content": "import \"engine_sim.mr\"\n\nunits units()\nconstants constants()\nimpulse_response_library ir_lib()\n\nprivate node wires {\n    output wire1: ignition_wire();\n    output wire2: ignition_wire();\n}\n\npublic node kohler_ch750 {\n    alias output __out: engine;\n\n    engine engine(\n        name: \"Kohler CH750\",\n        starter_torque: 50 * units.lb_ft,\n        starter_speed: 500 * units.rpm,\n        redline: 3600 * units.rpm\n    )\n\n    wires wires()\n\n    crankshaft c0(\n        throw: 69 * units.mm / 2,\n        flywheel_mass: 5 * units.lb,\n        mass: 5 * units.lb,\n        friction_torque: 10.0 * units.lb_ft,\n        moment_of_inertia: 0.22986844776863666 * 0.5,\n        position_x: 0.0,\n        position_y: 0.0,\n        tdc: constants.pi / 4\n    )\n\n    rod_journal rj0(angle: 0.0)\n    c0\n        .add_rod_journal(rj0)\n\n    piston_parameters piston_params(\n        mass: 400 * units.g,\n        //blowby: k_28inH2O(0.1),\n        compression_height: 1.0 * units.inch,\n        wrist_pin_position: 0.0,\n        displacement: 0.0\n    )\n\n    connecting_rod_parameters cr_params(\n        mass: 300.0 * units.g,\n        moment_of_inertia: 0.0015884918028487504,\n        center_of_mass: 0.0,\n        length: 4.0 * units.inch\n    )\n\n    cylinder_bank_parameters bank_params(\n        bore: 83 * units.mm,\n        deck_height: (4.0 + 1) * units.inch + 69 * units.mm / 2\n    )\n\n    intake intake(\n        plenum_volume: 1.0 * units.L,\n        plenum_cross_section_area: 10.0 * units.cm2,\n        intake_flow_rate: k_carb(50.0),\n        idle_flow_rate: k_carb(0.0),\n        idle_throttle_plate_position: 0.96,\n        throttle_gamma: 1.0\n    )\n\n    exhaust_system_parameters es_params(\n        outlet_flow_rate: k_carb(300.0),\n        primary_tube_length: 10.0 * units.inch,\n        primary_flow_rate: k_carb(200.0),\n        velocity_decay: 1.0,\n        volume: 20.0 * units.L\n    )\n\n    exhaust_system exhaust0(\n        es_params,\n        audio_volume: 1.0,\n        impulse_response: ir_lib.default_0\n    )\n\n    cylinder_bank b0(bank_params, angle: -45 * units.deg)\n    b0\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire1\n        )\n\n    cylinder_bank b1(bank_params, angle: 45.0 * units.deg)\n    b1\n        .add_cylinder(\n            piston: piston(piston_params, blowby: k_28inH2O(0.1)),\n            connecting_rod: connecting_rod(cr_params),\n            rod_journal: rj0,\n            intake: intake,\n            exhaust_system: exhaust0,\n            ignition_wire: wires.wire2\n        )\n\n    engine\n        .add_cylinder_bank(b0)\n        .add_cylinder_bank(b1)\n\n    engine.add_crankshaft(c0)\n\n    harmonic_cam_lobe lobe(\n        duration_at_50_thou: 160 * units.deg,\n        gamma: 1.1,\n        lift: 200 * units.thou,\n        steps: 100\n    )\n\n    vtwin90_camshaft_builder camshaft(\n        lobe_profile: lobe,\n        lobe_separation: 114 * units.deg,\n        base_radius: 500 * units.thou\n    )\n\n    b0.set_cylinder_head (\n        generic_small_engine_head(\n            chamber_volume: 50 * units.cc,\n            intake_camshaft: camshaft.intake_cam_0,\n            exhaust_camshaft: camshaft.exhaust_cam_0\n        )\n    )\n    b1.set_cylinder_head (\n        generic_small_engine_head(\n            chamber_volume: 50 * units.cc,\n            intake_camshaft: camshaft.intake_cam_1,\n            exhaust_camshaft: camshaft.exhaust_cam_1,\n            flip_display: true\n        )\n    )\n\n    function timing_curve(1000 * units.rpm)\n    timing_curve\n        .add_sample(0000 * units.rpm, 50 * units.deg)\n        .add_sample(1000 * units.rpm, 50 * units.deg)\n        .add_sample(2000 * units.rpm, 50 * units.deg)\n        .add_sample(3000 * units.rpm, 50 * units.deg)\n        .add_sample(4000 * units.rpm, 50 * units.deg)\n\n    engine.add_ignition_module(\n        vtwin90_distributor(\n            wires: wires,\n            timing_curve: timing_curve,\n            rev_limit: 5000 * units.rpm\n        ))\n}\n"
  },
  {
    "path": "assets/main.mr",
    "content": "import \"engine_sim.mr\"\nimport \"themes/default.mr\"\nimport \"engines/atg-video-2/01_subaru_ej25_eh.mr\"\n\nuse_default_theme()\nmain()\n"
  },
  {
    "path": "assets/part-library/part_library.mr",
    "content": "public import \"parts/cam_lobes.mr\"\npublic import \"parts/camshafts.mr\"\npublic import \"parts/heads.mr\"\npublic import \"parts/intakes.mr\"\npublic import \"parts/ignition_modules.mr\"\n"
  },
  {
    "path": "assets/part-library/parts/cam_lobes.mr",
    "content": "private import \"engine_sim.mr\"\n\nunits units()\n\nprivate node add_sym_sample {\n    input angle;\n    input lift;\n    input this;\n    alias output __out: this;\n\n    this.add_sample(angle * units.deg, lift * units.thou)\n    this.add_sample(-angle * units.deg, lift * units.thou)\n}\n\npublic node stock_454_intake_lobe_profile {\n    alias output __out:\n        harmonic_cam_lobe(\n            duration_at_50_thou: 194 * units.deg,\n            gamma: 0.8,\n            lift: 390 * units.thou,\n            steps: 100\n        );\n}\n\npublic node stock_454_exhaust_lobe_profile {\n    alias output __out:\n        harmonic_cam_lobe(\n            duration_at_50_thou: 202 * units.deg,\n            gamma: 0.8,\n            lift: 409 * units.thou,\n            steps: 100\n        );\n}\n\npublic node comp_cams_magnum_11_450_8_lobe_profile {\n    alias output __out:\n        harmonic_cam_lobe(\n            duration_at_50_thou: 232 * units.deg,\n            gamma: 0.75,\n            lift: 578 * units.thou,\n            steps: 100\n        );\n}\n\npublic node comp_cams_magnum_11_470_8_lobe_profile {\n    alias output __out:\n        harmonic_cam_lobe(\n            duration_at_50_thou: 252 * units.deg,\n            gamma: 0.8,\n            lift: 612 * units.thou,\n            steps: 100\n        );\n}\n"
  },
  {
    "path": "assets/part-library/parts/camshafts.mr",
    "content": "private import \"cam_lobes.mr\"\n\nprivate import \"engine_sim.mr\"\n\nunits units()\n\npublic node chevy_bbc_camshaft_builder {\n    input lobe_profile: stock_454_intake_lobe_profile();\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114.0 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;\n    input advance: 0.0 * units.deg;\n    input base_radius: 0.75 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_0: _exhaust_cam_0;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params(\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot90(90 * units.deg)\n    label rot360(360 * units.deg)\n\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center)\n        .add_lobe(rot360 - exhaust_lobe_center + 3 * rot90)\n        .add_lobe(rot360 - exhaust_lobe_center + 5 * rot90)\n        .add_lobe(rot360 - exhaust_lobe_center + 6 * rot90)\n\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + 7 * rot90)\n        .add_lobe(rot360 - exhaust_lobe_center + 2 * rot90)\n        .add_lobe(rot360 - exhaust_lobe_center + 4 * rot90)\n        .add_lobe(rot360 - exhaust_lobe_center + 1 * rot90)\n\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center)\n        .add_lobe(rot360 + intake_lobe_center + 3 * rot90)\n        .add_lobe(rot360 + intake_lobe_center + 5 * rot90)\n        .add_lobe(rot360 + intake_lobe_center + 6 * rot90)\n\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + 7 * rot90)\n        .add_lobe(rot360 + intake_lobe_center + 2 * rot90)\n        .add_lobe(rot360 + intake_lobe_center + 4 * rot90)\n        .add_lobe(rot360 + intake_lobe_center + 1 * rot90)\n}\n\npublic node vtwin90_camshaft_builder {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114.0 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;\n    input advance: 0.0 * units.deg;\n    input base_radius: 0.75 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_0: _exhaust_cam_0;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params(\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot90(90 * units.deg)\n    label rot360(360 * units.deg)\n\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center)\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + 3 * rot90)\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center)\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + 3 * rot90)\n}\n\npublic node chevy_454_stock_camshaft {\n    alias output __out:\n        chevy_bbc_camshaft_builder(\n            advance: 0 * units.deg,\n            intake_lobe_profile: stock_454_intake_lobe_profile(),\n            exhaust_lobe_profile: stock_454_exhaust_lobe_profile(),\n            intake_lobe_center: 108 * units.deg,\n            exhaust_lobe_center: 113 * units.deg);\n}\n\npublic node comp_cams_magnum_11_450_8 {\n    alias output __out:\n        chevy_bbc_camshaft_builder(\n            lobe_profile: comp_cams_magnum_11_450_8_lobe_profile(),\n            lobe_separation: 110 * units.deg,\n            advance: 4.0 * units.deg,\n            base_radius: 1000.0 * units.thou);\n}\n\npublic node comp_cams_magnum_11_470_8 {\n    alias output __out:\n        chevy_bbc_camshaft_builder(\n            lobe_profile: comp_cams_magnum_11_470_8_lobe_profile(),\n            lobe_separation: 110 * units.deg,\n            advance: 4.0 * units.deg,\n            base_radius: 1000.0 * units.thou);\n}\n"
  },
  {
    "path": "assets/part-library/parts/heads.mr",
    "content": "private import \"engine_sim.mr\"\n\nunits units()\n\nprivate node add_flow_sample {\n    input lift;\n    input flow;\n    input this;\n    alias output __out: this;\n\n    this.add_sample(lift * units.thou, k_28inH2O(flow))\n}\n\npublic node chevy_bbc_peanut_port_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0, 0)\n        .add_flow_sample(50, 25)\n        .add_flow_sample(100, 75)\n        .add_flow_sample(150, 100)\n        .add_flow_sample(200, 130)\n        .add_flow_sample(250, 180)\n        .add_flow_sample(300, 190)\n        .add_flow_sample(350, 220)\n        .add_flow_sample(400, 240)\n        .add_flow_sample(450, 250)\n        .add_flow_sample(500, 260)\n        .add_flow_sample(550, 260)\n        .add_flow_sample(600, 260)\n        .add_flow_sample(650, 255)\n        .add_flow_sample(700, 250)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0, 0)\n        .add_flow_sample(50, 25)\n        .add_flow_sample(100, 50)\n        .add_flow_sample(150, 75)\n        .add_flow_sample(200, 100)\n        .add_flow_sample(250, 125)\n        .add_flow_sample(300, 160)\n        .add_flow_sample(350, 175)\n        .add_flow_sample(400, 180)\n        .add_flow_sample(450, 190)\n        .add_flow_sample(500, 200)\n        .add_flow_sample(550, 205)\n        .add_flow_sample(600, 210)\n        .add_flow_sample(650, 210)\n        .add_flow_sample(700, 210)\n\n    cylinder_head head(\n        chamber_volume: 118.0 * units.cc,\n        intake_runner_volume: 189.0 * units.cc,\n        intake_runner_cross_section_area: 37.8 * units.cm2,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        intake_camshaft: intake_camshaft,\n        exhaust_camshaft: exhaust_camshaft,\n        flip_display: flip_display\n    )\n}\n\npublic node edelbrock_6055_rectangle_port {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0, 0)\n        .add_flow_sample(50, 25)\n        .add_flow_sample(100, 76)\n        .add_flow_sample(150, 100)\n        .add_flow_sample(200, 146)\n        .add_flow_sample(250, 175)\n        .add_flow_sample(300, 212)\n        .add_flow_sample(350, 230)\n        .add_flow_sample(400, 255)\n        .add_flow_sample(450, 275)\n        .add_flow_sample(500, 294)\n        .add_flow_sample(550, 300)\n        .add_flow_sample(600, 314)\n        .add_flow_sample(650, 314)\n        .add_flow_sample(700, 314)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0, 0)\n        .add_flow_sample(50, 25)\n        .add_flow_sample(100, 70)\n        .add_flow_sample(150, 100)\n        .add_flow_sample(200, 132)\n        .add_flow_sample(250, 140)\n        .add_flow_sample(300, 156)\n        .add_flow_sample(350, 170)\n        .add_flow_sample(400, 181)\n        .add_flow_sample(450, 191)\n        .add_flow_sample(500, 207)\n        .add_flow_sample(550, 214)\n        .add_flow_sample(600, 228)\n        .add_flow_sample(650, 228)\n        .add_flow_sample(700, 228)\n\n    cylinder_head head(\n        chamber_volume: 118.0 * units.cc,\n        intake_runner_volume: 315.0 * units.cc,\n        intake_runner_cross_section_area: 78.75 * units.cm2,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        intake_camshaft: intake_camshaft,\n        exhaust_camshaft: exhaust_camshaft,\n        flip_display: flip_display\n    )\n}\n\npublic node generic_small_engine_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 100.0 * units.cc;\n    input intake_runner_volume: 100.0 * units.cc;\n    input intake_runner_cross_section_area: 30.0 * units.cm2;\n    input exhaust_runner_volume: 100.0 * units.cc;\n    input exhaust_runner_cross_section_area: 30.0 * units.cm2;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 25 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 75 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 100 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 130 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 180 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 190 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 220 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 240 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 250 * flow_attenuation)\n        .add_flow_sample(500 * lift_scale, 260 * flow_attenuation)\n        .add_flow_sample(550 * lift_scale, 260 * flow_attenuation)\n        .add_flow_sample(600 * lift_scale, 260 * flow_attenuation)\n        .add_flow_sample(650 * lift_scale, 255 * flow_attenuation)\n        .add_flow_sample(700 * lift_scale, 250 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 25 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 50 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 75 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 100 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 125 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 175 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 180 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 190 * flow_attenuation)\n        .add_flow_sample(500 * lift_scale, 200 * flow_attenuation)\n        .add_flow_sample(550 * lift_scale, 205 * flow_attenuation)\n        .add_flow_sample(600 * lift_scale, 210 * flow_attenuation)\n        .add_flow_sample(650 * lift_scale, 210 * flow_attenuation)\n        .add_flow_sample(700 * lift_scale, 210 * flow_attenuation)\n\n    cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        intake_camshaft: intake_camshaft,\n        exhaust_camshaft: exhaust_camshaft,\n        flip_display: flip_display\n    )\n}\n"
  },
  {
    "path": "assets/part-library/parts/ignition_modules.mr",
    "content": "private import \"engine_sim.mr\"\n\nunits units()\nlabel cycle(2 * 360 * units.deg)\n\npublic node chevy_bbc_distributor {\n    input wires;\n    input timing_curve;\n    input rev_limit: 5500 * units.rpm;\n    alias output __out:\n        ignition_module(timing_curve: timing_curve, rev_limit: rev_limit)\n            .connect_wire(wires.wire1, (0.0 / 8.0) * cycle)\n            .connect_wire(wires.wire8, (1.0 / 8.0) * cycle)\n            .connect_wire(wires.wire4, (2.0 / 8.0) * cycle)\n            .connect_wire(wires.wire3, (3.0 / 8.0) * cycle)\n            .connect_wire(wires.wire6, (4.0 / 8.0) * cycle)\n            .connect_wire(wires.wire5, (5.0 / 8.0) * cycle)\n            .connect_wire(wires.wire7, (6.0 / 8.0) * cycle)\n            .connect_wire(wires.wire2, (7.0 / 8.0) * cycle);\n}\n\npublic node chevy_sbc_distributor {\n    input wires;\n    input timing_curve;\n    input rev_limit: 5500 * units.rpm;\n    alias output __out: chevy_bbc_distributor(\n        wires: wires,\n        timing_curve: timing_curve,\n        rev_limit: rev_limit\n    );\n}\n\npublic node vtwin90_distributor {\n    input wires;\n    input timing_curve;\n    input rev_limit: 5500 * units.rpm;\n    alias output __out:\n        ignition_module(timing_curve: timing_curve, rev_limit: rev_limit)\n            .connect_wire(wires.wire1, (0.0 / 8.0) * cycle)\n            .connect_wire(wires.wire2, (3.0 / 8.0) * cycle);\n}\n"
  },
  {
    "path": "assets/part-library/parts/intakes.mr",
    "content": "private import \"engine_sim.mr\"\n\nunits units()\n\npublic node chevy_bbc_stock_intake {\n    input carburetor_cfm: 650.0;\n    input idle_flow_rate_cfm: 1.0;\n    input idle_throttle_plate_position: 0.975;\n    input throttle_gamma: 2.0;\n\n    alias output __out: intake;\n\n    intake intake(\n        plenum_volume: 2.0 * units.L,\n        plenum_cross_section_area: 100.0 * units.cm2,\n        intake_flow_rate: k_carb(carburetor_cfm),\n        idle_flow_rate: k_carb(idle_flow_rate_cfm),\n        idle_throttle_plate_position: idle_throttle_plate_position,\n        throttle_gamma: throttle_gamma,\n        runner_flow_rate: k_carb(300.0),\n        runner_length: 6.0 * units.inch,\n        velocity_decay: 1.0\n    )\n}\n\npublic node performer_rpm_intake {\n    input carburetor_cfm: 650.0;\n    input idle_flow_rate_cfm: 1.0;\n    input idle_throttle_plate_position: 0.975;\n    input throttle_gamma: 2.0;\n\n    alias output __out: intake;\n\n    intake intake(\n        plenum_volume: 2.0 * units.L,\n        plenum_cross_section_area: 100.0 * units.cm2,\n        intake_flow_rate: k_carb(carburetor_cfm),\n        idle_flow_rate: k_carb(idle_flow_rate_cfm),\n        idle_throttle_plate_position: idle_throttle_plate_position,\n        throttle_gamma: throttle_gamma,\n        runner_flow_rate: k_carb(500.0),\n        runner_length: 6.0 * units.inch,\n        velocity_decay: 0.1\n    )\n}\n"
  },
  {
    "path": "assets/themes/amateur.mr",
    "content": "import \"engine_sim.mr\"\n\nunit_names units()\npublic node use_amateur_theme {\n    input start_fullscreen: false;\n    input speed_units: units.mph;\n    input pressure_units: units.inHg;\n    input torque_units: units.lb_ft;\n    input power_units: units.hp;\n\n    set_application_settings(\n        start_fullscreen: start_fullscreen,\n        speed_units: speed_units,\n        pressure_units: pressure_units,\n        torque_units:torque_units,\n        power_units: power_units,\n        \n        // Default Color Settings\n        color_background: 0x000000,\n        color_foreground: 0xFFFFFF,\n        color_highlight1: 0xFF0000,\n        color_highlight2: 0xFFFFFF,\n        color_shadow: 0x000000,\n        color_pink: 0xFF00FF,\n        color_red: 0xFF0000,\n        color_orange: 0xFF8000,\n        color_yellow: 0xFFFF00,\n        color_blue: 0x0000FF,\n        color_green: 0x00FF00\n    )\n}\n"
  },
  {
    "path": "assets/themes/bubble_gum.mr",
    "content": "import \"engine_sim.mr\"\n\nunit_names units()\npublic node use_bubble_gum_theme {\n    input start_fullscreen: false;\n    input speed_units: units.mph;\n    input pressure_units: units.inHg;\n    input torque_units: units.lb_ft;\n    input power_units: units.hp;\n\n    set_application_settings(\n        start_fullscreen: start_fullscreen,\n        speed_units: speed_units,\n        pressure_units: pressure_units,\n        torque_units:torque_units,\n        power_units: power_units,\n        \n        // Default Color Settings\n        color_background: 0xF394BE,\n        color_foreground: 0xFFFFFF,\n        color_highlight1: 0xfdeaf2,\n        color_highlight2: 0xFFFFFF,\n        color_shadow: 0xF394BE,\n        color_pink: 0xfad4e5,\n        color_red: 0xf5a9cb,\n        color_orange: 0xffd086,\n        color_yellow: 0xffd0c3,\n        color_blue: 0xcfd2dc,\n        color_green: 0xd7f7d2\n    )\n}\n"
  },
  {
    "path": "assets/themes/default.mr",
    "content": "import \"engine_sim.mr\"\n\nunit_names units()\npublic node use_default_theme {\n    input start_fullscreen: false;\n    input speed_units: units.mph;\n    input pressure_units: units.inHg;\n    input torque_units: units.lb_ft;\n    input power_units: units.hp;\n\n    set_application_settings(\n        start_fullscreen: start_fullscreen,\n        speed_units: speed_units,\n        pressure_units: pressure_units,\n        torque_units: torque_units,\n        power_units: power_units,\n        \n        // Default Color Settings\n        color_background: 0x0E1012,\n        color_foreground: 0xFFFFFF,\n        color_shadow: 0x0E1012,\n        color_highlight1: 0xEF4545,\n        color_highlight2: 0xFFFFFF,\n        color_pink: 0xF394BE,\n        color_red: 0xEE4445,\n        color_orange: 0xF4802A,\n        color_yellow: 0xFDBD2E,\n        color_blue: 0x77CEE0,\n        color_green: 0xBDD869\n    )\n}\n"
  },
  {
    "path": "assets/themes/minimalistic.mr",
    "content": "import \"engine_sim.mr\"\n\nunit_names units()\npublic node use_minimalistic_theme {\n    input start_fullscreen: false;\n    input speed_units: units.mph;\n    input pressure_units: units.inHg;\n    input torque_units: units.lb_ft;\n    input power_units: units.hp;\n\n    set_application_settings(\n        start_fullscreen: start_fullscreen,\n        speed_units: speed_units,\n        pressure_units: pressure_units,\n        torque_units:torque_units,\n        power_units: power_units,\n        \n        // Default Color Settings\n        color_background: 0x000000,\n        color_foreground: 0xFFFFFF,\n        color_highlight1: 0xFFFFFF,\n        color_highlight2: 0xFFFFFF,\n        color_shadow: 0x000000,\n        color_pink: 0xFFFFFF,\n        color_red: 0xFFFFFF,\n        color_orange: 0xFFFFFF,\n        color_yellow: 0xFFFFFF,\n        color_blue: 0xFFFFFF,\n        color_green: 0xFFFFFF\n    )\n}\n"
  },
  {
    "path": "assets/themes/night_vision.mr",
    "content": "import \"engine_sim.mr\"\n\nunit_names units()\npublic node use_night_vision_theme {\n    input start_fullscreen: false;\n    input speed_units: units.mph;\n    input pressure_units: units.inHg;\n    input torque_units: units.lb_ft;\n    input power_units: units.hp;\n    input color: 0x4dff68;\n\n    set_application_settings(\n        start_fullscreen: start_fullscreen,\n        speed_units: speed_units,\n        pressure_units: pressure_units,\n        torque_units:torque_units,\n        power_units: power_units,\n        \n        // Default Color Settings\n        color_background: 0x000000,\n        color_foreground: color,\n        color_highlight1: color,\n        color_highlight2: color,\n        color_shadow: 0x000000,\n        color_pink: color,\n        color_red: color,\n        color_orange: color,\n        color_yellow: color,\n        color_blue: color,\n        color_green: color\n    )\n}\n"
  },
  {
    "path": "assets/themes/paper.mr",
    "content": "import \"engine_sim.mr\"\n\nunit_names units()\npublic node use_paper_theme {\n    input start_fullscreen: false;\n    input speed_units: units.mph;\n    input pressure_units: units.inHg;\n    input torque_units: units.lb_ft;\n    input power_units: units.hp;\n    input color: 0x63aaff;\n\n    set_application_settings(\n        start_fullscreen: start_fullscreen,\n        speed_units: speed_units,\n        pressure_units: pressure_units,\n        torque_units:torque_units,\n        power_units: power_units,\n        \n        // Default Color Settings\n        color_background: 0xf8f2f0,\n        color_foreground: color,\n        color_highlight1: color,\n        color_highlight2: color,\n        color_shadow: 0xf8f2f0,\n        color_pink: color,\n        color_red: color,\n        color_orange: color,\n        color_yellow: color,\n        color_blue: color,\n        color_green: color\n    )\n}\n"
  },
  {
    "path": "configuration/delta.conf",
    "content": "/delta\n/assets"
  },
  {
    "path": "dependencies/CMakeLists.txt",
    "content": "add_subdirectory(submodules)\n\nif (DISCORD_ENABLED)\n    add_subdirectory(discord)\nendif ()\n"
  },
  {
    "path": "dependencies/discord/CMakeLists.txt",
    "content": "set_property(TARGET discord PROPERTY FOLDER \"discord\")\n"
  },
  {
    "path": "dependencies/discord/Discord.cpp",
    "content": "﻿#include \"Discord.h\"\n#include <stdio.h>\n#include <time.h>\n\n\nconst char* DISCORD_APPLICATION_ID = \"1008609936256811038\"; // https://discordapp.com/developers/applications\n\nCDiscord* CDiscord::m_pInstance = NULL;\nstatic int64_t StartTime;\n\n\nCDiscord::CDiscord()\n{\n\tm_state = \"Doing Things\";\n\tm_bEnableDiscord = true;\n\tm_bConnected = false;\n}\n\nCDiscord::~CDiscord()\n{\n}\n\nbool CDiscord::CreateInstance()\n{\n\tif (m_pInstance)\n\t\treturn false;\n\n\tm_pInstance = new CDiscord;\n\n\treturn true;\n}\n\nvoid CDiscord::DestroyInstance()\n{\n\tif (!m_pInstance)\n\t\treturn;\n\n\tDiscord_Shutdown(); // shut down discord\n\n\tdelete m_pInstance;\n}\n\nvoid CDiscord::handleDiscordReady(const DiscordUser * connectedUser)\n{\n\tGetDiscordManager()->SetConnected(true);\n}\n\nvoid CDiscord::handleDiscordDisconnected(int errcode, const char* message)\n{\n\tGetDiscordManager()->SetConnected(false);\n}\n\nvoid CDiscord::handleDiscordError(int errcode, const char* message)\n{\n}\n\n\n\nvoid CDiscord::InitDiscord()\n{\n\tDiscordEventHandlers handlers;\n\tmemset(&handlers, 0, sizeof(handlers));\n\n\thandlers.ready = CDiscord::handleDiscordReady;\n\thandlers.disconnected = CDiscord::handleDiscordDisconnected;\n\thandlers.errored = CDiscord::handleDiscordError;\n\thandlers.joinGame = NULL;\n\thandlers.spectateGame = NULL;\n\thandlers.joinRequest = NULL;\n\tDiscord_Initialize(DISCORD_APPLICATION_ID, &handlers, 1, NULL);\n}\n\nvoid CDiscord::UpdatePresence()\n{\n\tif (m_bEnableDiscord == false)\n\t\treturn;\n\n\tif (m_bConnected == false)\n\t\treturn;\n\n\t\tDiscordRichPresence discordPresence;\n\t\tmemset(&discordPresence, 0, sizeof(discordPresence));\n\n\t\tdiscordPresence.details = m_state.c_str();\n\n\t\tdiscordPresence.largeImageKey = \"atgenginesim\";\n\t\tdiscordPresence.largeImageText = \"Engine Simulator\";\n\t\t\n\n\t\tdiscordPresence.startTimestamp = StartTime;\n\n\t\tdiscordPresence.instance = 0;\n\t\tDiscord_UpdatePresence(&discordPresence);\n}\n\n\nvoid CDiscord::ClearPresence()\n{\n\tDiscord_ClearPresence();\n}\n\n\nvoid CDiscord::UpdateDiscordConnection()\n{\n\tif (m_bEnableDiscord == false)\n\t\treturn;\n\n#ifdef DISCORD_DISABLE_IO_THREAD\n\tDiscord_UpdateConnection();\n#endif\n\tDiscord_RunCallbacks();\n}\n\nvoid CDiscord::SetUseDiscord(bool bFlag)\n{\n\tm_bEnableDiscord = bFlag;\n\n\tif (bFlag)\n\t{\n\t\tInitDiscord();\n\t}\n}\n\n\nvoid CDiscord::SetConnected(bool bFlag)\n{\n\tm_bConnected = bFlag;\n}\n\nvoid CDiscord::SetStatus(DiscordRichPresence presence, std::string engineName = \"\", std::string buildVersion = \"\")\n{\n\tstd::string engineString = \"Engine: \" + engineName;\n\tstd::string stateString = \"Build: \" + buildVersion;\n\tpresence.state = stateString.c_str();\n\tpresence.details = engineString.c_str();\n\tpresence.largeImageKey = \"atgenginesim\";\n\tpresence.largeImageText = \"Engine Simulator\";\n\tStartTime = time(NULL);\n\tpresence.instance = 1;\n\tpresence.startTimestamp = StartTime;\n\tDiscord_UpdatePresence(&presence);\n}\n"
  },
  {
    "path": "dependencies/discord/Discord.h",
    "content": "﻿/*****************************************************************************\n*\n* File\t\t\t: Discord.h\n* Author\t\t: SanGawku\n* Copyright\t\t: SanGawku\n* Date\t\t\t: 7/29/2019\n* Abstract\t\t: Discord\n*****************************************************************************\n*\n* Desc         : Discord rich presence integration\n*\n*****************************************************************************/\n#ifndef __DISCORD_H__\n#define __DISCORD_H__\n\n#include <string>\n#include \"discord_rpc.h\"\n#pragma comment(lib, \"discord-rpc.lib\")\n\nclass CDiscord\n{\n\npublic:\n\n\tCDiscord();\n\tvirtual ~CDiscord();\n\n\tstatic bool\t\tCreateInstance();\n\tstatic void\t\tDestroyInstance();\n\n\tstatic CDiscord* GetInstance() { return m_pInstance; }\n\npublic:\n\n\tstatic void handleDiscordReady(const DiscordUser* connectedUser);\n\n\tstatic void handleDiscordDisconnected(int errcode, const char* message);\n\n\tstatic void handleDiscordError(int errcode, const char* message);\n\nprivate:\n\n\tvoid InitDiscord();\n\n\tvoid UpdatePresence();\n\n\tvoid ClearPresence();\n\npublic:\n\n\tvoid\t\t\tUpdateDiscordConnection();\n\n\tvoid\t\t\tSetUseDiscord(bool bFlag); // if true, then we will use discord and connect to discord\n\n\tvoid\t\t\tSetConnected(bool bFlag); //set connected to discord or not\n\n\tvoid\t\t\tSetStatus(DiscordRichPresence presence, std::string engineName, std::string buildVersion);\n\n\n\nprivate:\n\n\tstd::string\t\tm_state;\n\t\n\tstd::string\t\tm_engineName;\n\n\tbool\t\t\tm_bEnableDiscord;\n\n\tbool\t\t\tm_bConnected;\n\nprotected:\n\n\tstatic CDiscord* m_pInstance;\n};\n\nstatic CDiscord* GetDiscordManager()\n{\n\treturn CDiscord::GetInstance();\n};\n\n\n#endif"
  },
  {
    "path": "dependencies/discord/LICENSE",
    "content": "Copyright 2017 Discord, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "dependencies/discord/discord_register.h",
    "content": "﻿#pragma once\n\n#if defined(DISCORD_DYNAMIC_LIB)\n#  if defined(_WIN32)\n#    if defined(DISCORD_BUILDING_SDK)\n#      define DISCORD_EXPORT __declspec(dllexport)\n#    else\n#      define DISCORD_EXPORT __declspec(dllimport)\n#    endif\n#  else\n#    define DISCORD_EXPORT __attribute__((visibility(\"default\")))\n#  endif\n#else\n#  define DISCORD_EXPORT\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nDISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command);\nDISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "dependencies/discord/discord_rpc.h",
    "content": "﻿#pragma once\n\n// clang-format off\n\n#if defined(DISCORD_DYNAMIC_LIB)\n#  if defined(_WIN32)\n#    if defined(DISCORD_BUILDING_SDK)\n#      define DISCORD_EXPORT __declspec(dllexport)\n#    else\n#      define DISCORD_EXPORT __declspec(dllimport)\n#    endif\n#  else\n#    define DISCORD_EXPORT __attribute__((visibility(\"default\")))\n#  endif\n#else\n#  define DISCORD_EXPORT\n#endif\n\n// clang-format on\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct DiscordRichPresence {\n    const char* state;   /* max 128 bytes */\n    const char* details; /* max 128 bytes */\n\tlong long startTimestamp;\n\tlong long endTimestamp;\n    const char* largeImageKey;  /* max 32 bytes */\n    const char* largeImageText; /* max 128 bytes */\n    const char* smallImageKey;  /* max 32 bytes */\n    const char* smallImageText; /* max 128 bytes */\n    const char* partyId;        /* max 128 bytes */\n    int partySize;\n    int partyMax;\n    const char* matchSecret;    /* max 128 bytes */\n    const char* joinSecret;     /* max 128 bytes */\n    const char* spectateSecret; /* max 128 bytes */\n\tunsigned char instance;\n} DiscordRichPresence;\n\ntypedef struct DiscordUser {\n    const char* userId;\n    const char* username;\n    const char* discriminator;\n    const char* avatar;\n} DiscordUser;\n\ntypedef struct DiscordEventHandlers {\n    void (*ready)(const DiscordUser* request);\n    void (*disconnected)(int errorCode, const char* message);\n    void (*errored)(int errorCode, const char* message);\n    void (*joinGame)(const char* joinSecret);\n    void (*spectateGame)(const char* spectateSecret);\n    void (*joinRequest)(const DiscordUser* request);\n} DiscordEventHandlers;\n\n#define DISCORD_REPLY_NO 0\n#define DISCORD_REPLY_YES 1\n#define DISCORD_REPLY_IGNORE 2\n\nDISCORD_EXPORT void Discord_Initialize(const char* applicationId,\n                                       DiscordEventHandlers* handlers,\n                                       int autoRegister,\n                                       const char* optionalSteamId);\nDISCORD_EXPORT void Discord_Shutdown(void);\n\n/* checks for incoming messages, dispatches callbacks */\nDISCORD_EXPORT void Discord_RunCallbacks(void);\n\n/* If you disable the lib starting its own io thread, you'll need to call this from your own */\n#ifdef DISCORD_DISABLE_IO_THREAD\nDISCORD_EXPORT void Discord_UpdateConnection(void);\n#endif\n\nDISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence);\nDISCORD_EXPORT void Discord_ClearPresence(void);\n\nDISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply);\n\nDISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers);\n\n#ifdef __cplusplus\n} /* extern \"C\" */\n#endif\n"
  },
  {
    "path": "dependencies/submodules/CMakeLists.txt",
    "content": "add_subdirectory(delta-studio)\n\nset_property(TARGET delta-basic PROPERTY FOLDER \"delta\")\nset_property(TARGET delta-basic-demo PROPERTY FOLDER \"delta\")\nset_property(TARGET delta-core PROPERTY FOLDER \"delta\")\nset_property(TARGET delta-physics PROPERTY FOLDER \"delta\")\n\nadd_subdirectory(simple-2d-constraint-solver)\n\nset_property(TARGET simple-2d-constraint-solver PROPERTY FOLDER \"scs\")\nset_property(TARGET simple-2d-constraint-solver-test PROPERTY FOLDER \"scs\")\n\nadd_subdirectory(csv-io)\n\nset_property(TARGET csv-io PROPERTY FOLDER \"csv-io\")\nset_property(TARGET csv-io-main PROPERTY FOLDER \"csv-io\")\nset_property(TARGET csv-io-test PROPERTY FOLDER \"csv-io\")\n\nif (DTV)\n    add_subdirectory(direct-to-video)\n\n    set_property(TARGET direct-to-video PROPERTY FOLDER \"dtv\")\n    set_property(TARGET direct-to-video PROPERTY FOLDER \"dtv\")\nendif (DTV)\n\nadd_subdirectory(piranha)\n\nset_property(TARGET piranha PROPERTY FOLDER \"piranha\")\nset_property(TARGET piranha_test PROPERTY FOLDER \"piranha\")\nset_property(TARGET piranha_reference_compiler PROPERTY FOLDER \"piranha\")\n"
  },
  {
    "path": "es/actions/actions.mr",
    "content": "module {\n    @name:      \"Actions\"\n    @author:    \"ATG (Ange Yaghi)\"\n    @copyright: \"Copyright 2022, Ange Yaghi\"\n}\n\nprivate import \"../types/atomic_types.mr\"\nprivate import \"../types/conversions.mr\"\nprivate import \"../types/operations.mr\"\nprivate import \"../constants/constants.mr\"\nprivate import \"../constants/units.mr\"\nprivate import \"../objects/objects.mr\"\n\nconstants constants()\nunits units()\n\npublic node set_engine => __engine_sim__set_engine {\n    input engine [engine];\n}\n\nprivate node _add_rod_journal => __engine_sim__add_rod_journal {\n    input rod_journal [rod_journal];\n    input crankshaft [crankshaft];\n}\n\npublic node add_rod_journal {\n    input rod_journal;\n    input this;\n    alias output out: this;\n\n    _add_rod_journal(rod_journal, this)\n}\n\nprivate node _add_slave_journal => __engine_sim__add_slave_journal {\n    input rod_journal [rod_journal];\n    input rod [connecting_rod];\n}\n\npublic node add_slave_journal {\n    input rod_journal;\n    input this;\n    alias output out: this;\n\n    _add_slave_journal(rod_journal, this)\n}\n\npublic node _add_crankshaft => __engine_sim__add_crankshaft {\n    input crankshaft [crankshaft];\n    input engine [engine];\n}\n\npublic node add_crankshaft {\n    input crankshaft;\n    input this;\n    alias output out: this;\n\n    _add_crankshaft(crankshaft, engine: this)\n}\n\nprivate node _add_cylinder_bank => __engine_sim__add_cylinder_bank {\n    input engine [engine];\n    input cylinder_bank [cylinder_bank];\n}\n\npublic node add_cylinder_bank {\n    input cylinder_bank;\n    input this;\n    alias output __out: this;\n\n    _add_cylinder_bank(engine: this, cylinder_bank)\n}\n\nprivate node _add_cylinder => __engine_sim__add_cylinder {\n    input piston [piston];\n    input connecting_rod [connecting_rod];\n    input rod_journal [rod_journal];\n    input exhaust_system [exhaust_system];\n    input intake [intake];\n    input cylinder_bank [cylinder_bank];\n    input ignition_wire [ignition_wire];\n    input sound_attenuation [float];\n    input primary_length [float];\n}\n\npublic node add_cylinder {\n    input intake;\n    input exhaust_system;\n    input piston;\n    input connecting_rod;\n    input rod_journal;\n    input ignition_wire;\n    input sound_attenuation: 1.0;\n    input primary_length: 0.0;\n    input this;\n    alias output __out: this;\n\n    _add_cylinder(\n        piston: piston,\n        connecting_rod: connecting_rod,\n        rod_journal: rod_journal,\n        exhaust_system: exhaust_system,\n        intake: intake,\n        ignition_wire: ignition_wire,\n        cylinder_bank: this,\n        sound_attenuation: sound_attenuation,\n        primary_length: primary_length\n    )\n}\n\nprivate node _add_sample => __engine_sim__add_sample {\n    input x [float];\n    input y [float];\n    input function [function];\n}\n\npublic node add_sample {\n    input x;\n    input y;\n    input this;\n    alias output __out: this;\n\n    _add_sample(x: x, y: y, function: this)\n}\n\nprivate node _add_lobe => __engine_sim__add_lobe {\n    input centerline [float];\n    input camshaft [camshaft];\n}\n\npublic node add_lobe {\n    input centerline;\n    input this;\n    alias output __out: this;\n\n    _add_lobe(centerline: centerline, camshaft: this)\n}\n\nprivate node _set_cylinder_head => __engine_sim__set_cylinder_head {\n    input head [cylinder_head];\n    input bank [cylinder_bank];\n}\n\npublic node set_cylinder_head {\n    input head;\n    input this;\n\n    _set_cylinder_head(head: head, bank: this)\n}\n\npublic node k_28inH2O => __engine_sim__k_28inH2O {\n    input flow [float]: 1.0;\n    alias output __out [float];\n}\n\npublic node k_carb => __engine_sim__k_carb {\n    input flow [float]: 1.0;\n    alias output __out [float];\n}\n\npublic node circle_area {\n    input radius;\n    alias output __out:\n        constants.pi * radius * radius;\n}\n\npublic node _connect_wire => __engine_sim__connect_ignition_wire {\n    input wire [ignition_wire];\n    input ignition_module [ignition_module];\n    input angle [float];\n}\n\npublic node connect_wire {\n    input wire;\n    input angle;\n    input this;\n    alias output __out: this;\n\n    _connect_wire(wire: wire, angle: angle, ignition_module: this)\n}\n\nprivate node _add_ignition_module => __engine_sim__add_ignition_module {\n    input ignition_module [ignition_module];\n    input engine [engine];\n}\n\npublic node add_ignition_module {\n    input ignition_module;\n    input this;\n    alias output __out: this;\n\n    _add_ignition_module(ignition_module: ignition_module, engine: this)\n}\n\npublic node _generate_harmonic_cam_lobe => __engine_sim__generate_harmonic_cam_lobe {\n    input duration_at_50_thou [float];\n    input gamma [float];\n    input lift [float];\n    input steps [int];\n    input function [function];\n}\n\npublic node harmonic_cam_lobe {\n    input duration_at_50_thou: 0.0;\n    input gamma: 1.0;\n    input lift: 300 * units.thou;\n    input steps: 100;\n    alias output __out: generated_function;\n\n    function generated_function()\n    _generate_harmonic_cam_lobe(\n        duration_at_50_thou: duration_at_50_thou,\n        gamma: gamma,\n        lift: lift,\n        steps: steps,\n        function: generated_function\n    )    \n}\n\npublic node set_vehicle => __engine_sim__set_vehicle {\n    input vehicle [vehicle];\n}\n\nprivate node _add_gear => __engine_sim__add_gear {\n    input ratio [float];\n    input transmission [transmission];\n}\n\npublic node add_gear {\n    input ratio;\n    input this;\n    alias output __out: this;\n\n    _add_gear(ratio, this)\n}\n\npublic node set_transmission => __engine_sim__set_transmission {\n    input transmission [transmission];\n}\n"
  },
  {
    "path": "es/constants/constants.mr",
    "content": "module {\n    @name:      \"Units\"\n    @author:    \"ATG (Ange Yaghi)\"\n    @copyright: \"Copyright 2022, Ange Yaghi\"\n}\n\nprivate import \"../types/atomic_types.mr\"\nprivate import \"../types/conversions.mr\"\n\npublic node constants {\n    output pi: 3.14159265359;\n}\n"
  },
  {
    "path": "es/constants/units.mr",
    "content": "module {\n    @name:      \"Units\"\n    @author:    \"ATG (Ange Yaghi)\"\n    @copyright: \"Copyright 2022, Ange Yaghi\"\n}\n\nprivate import \"constants.mr\"\n\nprivate import \"../types/atomic_types.mr\"\nprivate import \"../types/conversions.mr\"\nprivate import \"../types/operations.mr\"\n\nconstants constants()\npublic node units {\n    // Force\n    output N: 1.0;\n    output lbf: N * 4.44822;\n\n    // Mass\n    output kg: 1.0;\n    output g: kg / 1000.0;\n\n    output lb: 0.45359237 * kg;\n\n    // Distance\n    output m: 1.0;\n    output cm: m / 100.0;\n    output mm: m / 1000.0;\n    output km: m * 1000.0;\n\n    output inch: cm * 2.54;\n    output foot: inch * 12.0;\n    output thou: inch / 1000.0;\n\n    output mile: m * 1609.344;\n\n    // Time\n    output sec: 1.0;\n    output minute: 60.0 * sec;\n    output hour: 60.0 * minute;\n\n    // Torque\n    output Nm: N * m;\n    output lb_ft: lbf * foot;\n\n    // Volume\n    output m3: 1.0;\n    output cc: cm * cm * cm;\n    output mL: cc;\n    output L: mL * 1000.0;\n    output cubic_feet: foot * foot * foot;\n    output cubic_inches: inch * inch * inch;\n    output gal: 3.785411784 * L;\n\n    // Molecular\n    output mol: 1.0;\n    output kmol: mol / 1000.0;\n    output mmol: kmol / 1000.0;\n    output lbmol: mol * 453.59237;\n\n    // Flow-rate\n    output mol_per_sec: mol / sec;\n    output scfm: 0.002641 * lbmol / minute;\n\n    // Area\n    output m2: 1.0;\n    output cm2: cm * cm;\n\n    // Pressure\n    output Pa: 1.0;\n    output kPa: Pa * 1000.0;\n    output MPa: kPa * 1000.0;\n    output atm: 101.325 * kPa;\n\n    output psi: lb / (inch * inch);\n    output psig: psi;\n    output inHg: Pa * 3386.3886666666713;\n    output inH2O: inHg * 0.0734824;\n\n    // Temperature\n    output K: 1.0;\n    output K0: 273.15;\n    output C: K;\n    output F: (5.0 / 9.0) * K;\n    output F0: -459.67;\n\n    // Energy\n    output J: 1.0;\n    output kJ: J * 1000.0;\n    output MJ: kJ * 1000.0;\n\n    // Angles\n    output rad: 1.0;\n    output deg: rad * (constants.pi / 180.0);\n\n    // RPM\n    output rpm: 0.104719755;\n\n    // Speed\n    output mph: mile / hour;\n}\n\npublic node unit_names {\n    // Pressure\n    output inHg: \"inHg\";\n    output mbar: \"mbar\";\n    output millibar: mbar;\n    output bar: \"bar\";\n    output kPa: \"kPa\";\n    output psi: \"psi\";\n\n    // Speed\n    output mph: \"mph\";\n    output kph: \"kph\";\n    output american: mph;\n    output murican: american;\n    output british: mph;\n    output european: kph;\n    output euro: european;\n\n    // Torque\n    output lb_ft: \"lb-ft\";\n    output ft_lb: lb_ft;\n    output Nm: \"Nm\";\n\n    // Power\n    output hp: \"hp\";\n    output kW: \"kW\";\n    output horsepower: hp;\n    output kilowatt: kW;\n}\n"
  },
  {
    "path": "es/engine_sim.mr",
    "content": "module {\n    @name:      \"Engine Simulator Library\"\n    @author:    \"ATG (Ange Yaghi)\"\n    @copyright: \"Copyright 2022, Ange Yaghi\"\n}\n\n// Types\npublic import \"types/atomic_types.mr\"\npublic import \"types/conversions.mr\"\npublic import \"types/operations.mr\"\n\n// Actions\npublic import \"actions/actions.mr\"\n\n// Objects\npublic import \"objects/objects.mr\"\n\n// Constants\npublic import \"constants/constants.mr\"\npublic import \"constants/units.mr\"\n\n// Infrastructure\npublic import \"infrastructure/infrastructure.mr\"\n\n// Library\npublic import \"part-library/part_library.mr\"\npublic import \"sound-library/impulse_responses.mr\"\n\n// Utilities\npublic import \"utilities/utilities.mr\"\n\n// Application settings\npublic import \"settings/application_settings.mr\"\n"
  },
  {
    "path": "es/infrastructure/infrastructure.mr",
    "content": "module {\n    @name:      \"Infrastructure\"\n    @author:    \"ATG (Ange Yaghi)\"\n    @copyright: \"Copyright 2022, Ange Yaghi\"\n}\n\npublic node label {\n    input in;\n    alias output _out: in;\n}\n"
  },
  {
    "path": "es/objects/objects.mr",
    "content": "module {\n    @name:      \"Objects\"\n    @author:    \"ATG (Ange Yaghi)\"\n    @copyright: \"Copyright 2022, Ange Yaghi\"\n}\n\nprivate import \"../types/atomic_types.mr\"\nprivate import \"../types/conversions.mr\"\nprivate import \"../types/operations.mr\"\nprivate import \"../constants/units.mr\"\nprivate import \"../actions/actions.mr\"\n\nunits units()\n\n// Channels\npublic node engine_channel => __engine_sim__engine_channel { /* void */ }\npublic node crankshaft_channel => __engine_sim__crankshaft_channel { /* void */ }\npublic node rod_journal_channel => __engine_sim__rod_journal { /* void */ }\npublic node connecting_rod_channel => __engine_sim__connecting_rod_channel { /* void */ }\npublic node piston_channel => __engine_sim__piston_channel { /* void */ }\npublic node cylinder_bank_channel => __engine_sim__cylinder_bank_channel { /* void */ }\npublic node function_channel => __engine_sim__function_channel { /* void */ }\npublic node cylinder_head_channel => __engine_sim__cylinder_head_channel { /* void */ }\npublic node camshaft_channel => __engine_sim__camshaft_channel { /* void */ }\npublic node intake_channel => __engine_sim__intake_channel { /* void */ }\npublic node exhaust_system_channel => __engine_sim__exhaust_system_channel { /* void */ }\npublic node ignition_module_channel => __engine_sim__ignition_module_channel { /* void */ }\npublic node ignition_wire_channel => __engine_sim__ignition_wire_channel { /* void */ }\npublic node fuel_channel => __engine_sim__fuel_channel { /* void */ }\npublic node impulse_response_channel => __engine_sim__impulse_response_channel { /* void */ }\npublic node valvetrain_channel => __engine_sim__valvetrain_channel { /* void */ }\npublic node vehicle_channel => __engine_sim__vehicle_channel { /* void */ }\npublic node transmission_channel => __engine_sim__transmission_channel { /* void */ }\npublic node throttle_channel => __engine_sim__throttle_channel { /* void */ }\n\nprivate node turbulence_to_flame_speed_ratio_default {\n    alias output __out:\n        function(5.0)\n            .add_sample(0.0, 3.0)\n            .add_sample(5.0, 1.5 * 5.0)\n            .add_sample(10.0, 1.5 * 10.0)\n            .add_sample(15.0, 1.5 * 15.0)\n            .add_sample(20.0, 1.5 * 20.0)\n            .add_sample(25.0, 1.5 * 25.0)\n            .add_sample(30.0, 1.5 * 30.0)\n            .add_sample(35.0, 1.5 * 35.0)\n            .add_sample(40.0, 1.5 * 40.0)\n            .add_sample(45.0, 1.5 * 45.0);\n}\n\npublic node fuel => __engine_sim__fuel {\n    input name [string]: \"Gasoline [Default]\";\n    input molecular_mass [float]: 100 * units.g;\n    input energy_density [float]: 48.1 * units.kJ / units.g;\n    input density [float]: 0.755 * units.kg / units.L;\n    input molecular_afr [float]: 25 / 2.0;\n    input turbulence_to_flame_speed_ratio [function]:\n        turbulence_to_flame_speed_ratio_default();\n    input max_burning_efficiency [float]: 0.8;\n    input burning_efficiency_randomness [float]: 0.5;\n    input low_efficiency_attenuation [float]: 0.6;\n    input max_turbulence_effect [float]: 2.0;\n    input max_dilution_effect [float]: 10.0;\n    alias output __out [fuel_channel];\n}\n\n// Engine\npublic node engine_parameters {\n    input name: \"\";\n    input redline: 6000 * units.rpm;\n    input starter_speed: 200 * units.rpm;\n    input starter_torque: 200 * units.lb_ft;\n    input fuel: fuel();\n}\n\nprivate node _engine => __engine_sim__engine {\n    input name [string];\n    \n    input redline [float];\n    input starter_speed [float];\n    input starter_torque [float];\n    input dyno_min_speed [float];\n    input dyno_max_speed [float];\n    input dyno_hold_step [float];\n\n    input fuel [fuel];\n    input throttle [throttle_channel];\n\n    input simulation_frequency [float];\n    input hf_gain [float];\n    input jitter [float];\n    input noise [float];\n\n    alias output __out [engine_channel];\n}\n\npublic node engine {\n    input params: engine_parameters();\n    input name: params.name;\n\n    input redline: params.redline;\n    input starter_speed: params.starter_speed;\n    input starter_torque: params.starter_torque;\n    input dyno_min_speed: 1000 * units.rpm;\n    input dyno_max_speed: redline;\n    input dyno_hold_step: 100 * units.rpm;\n\n    input fuel: params.fuel;\n    input throttle_gamma: 1.0;\n    input throttle:\n        direct_throttle_linkage(gamma: throttle_gamma);\n\n    input simulation_frequency: 10000;\n    input hf_gain: 0.01;\n    input jitter: 0.5;\n    input noise: 1.0;\n\n    alias output __out [_engine]:\n        _engine(\n            name: name,\n\n            redline: redline,\n            starter_speed: starter_speed,\n            starter_torque: starter_torque,\n            dyno_min_speed: dyno_min_speed,\n            dyno_max_speed: dyno_max_speed,\n            dyno_hold_step: dyno_hold_step,\n\n            fuel: fuel,\n            throttle: throttle,\n\n            simulation_frequency: simulation_frequency,\n            hf_gain: hf_gain,\n            jitter: jitter,\n            noise: noise\n        );\n}\n\npublic node direct_throttle_linkage => __engine_sim__direct_throttle_linkage {\n    input gamma [float]: 1.0;\n    alias output __out [throttle_channel];\n}\n\npublic node governor => __engine_sim__governor {\n    input min_speed [float]: 1.0;\n    input max_speed [float]: 1.0;\n    input min_v [float]: -2.0;\n    input max_v [float]: 2.0;\n    input k_s [float]: 1.0;\n    input k_d [float]: 300.0;\n    input gamma [float]: 0.1;\n    alias output __out [throttle_channel];\n}\n\n// Crankshaft\npublic node crankshaft_parameter_defaults {\n    input throw: 0.0;\n    input flywheel_mass: 0.0;\n    input mass: 0.0;\n    input friction_torque: 0.0;\n    input moment_of_inertia: 0.0;\n    input position_x: 0.0;\n    input position_y: 0.0;\n    input tdc: 0.0;\n}\n\nprivate node _crankshaft => __engine_sim__crankshaft {\n    input throw [float]: 0.0;\n    input flywheel_mass [float]: 0.0;\n    input mass [float]: 0.0;\n    input friction_torque [float]: 0.0;\n    input moment_of_inertia [float]: 0.0;\n    input position_x [float]: 0.0;\n    input position_y [float]: 0.0;\n    input tdc [float]: 0.0;\n    alias output __out [crankshaft_channel];\n}\n\npublic node crankshaft {\n    input params: crankshaft_parameter_defaults();\n    input throw: params.throw;\n    input flywheel_mass: params.flywheel_mass;\n    input mass: params.mass;\n    input friction_torque: params.friction_torque;\n    input moment_of_inertia: params.moment_of_inertia;\n    input position_x: params.position_x;\n    input position_y: params.position_y;\n    input tdc: params.tdc;\n    alias output __out [_crankshaft]:\n        _crankshaft(\n            throw: throw,\n            flywheel_mass: flywheel_mass,\n            mass: mass,\n            friction_torque: friction_torque,\n            moment_of_inertia: moment_of_inertia,\n            position_x: position_x,\n            position_y: position_y,\n            tdc: tdc\n        );\n}\n\n// Rod Journal\npublic node rod_journal => __engine_sim__rod_journal {\n    input angle [float]: 0;\n    alias output __out [rod_journal_channel];\n}\n\n// Connecting Rod\nprivate node connecting_rod_parameter_defaults {\n    input mass: 0.0;\n    input moment_of_inertia: 0.0;\n    input center_of_mass: 0.0;\n    input length: 0.0;\n    input slave_throw: 0.0;\n}\n\npublic node connecting_rod_parameters {\n    input copy: connecting_rod_parameter_defaults();\n    input mass: copy.mass;\n    input moment_of_inertia: copy.moment_of_inertia;\n    input center_of_mass: copy.center_of_mass;\n    input length: copy.length;\n    input slave_throw: copy.slave_throw;\n}\n\nprivate node _connecting_rod => __engine_sim__connecting_rod {\n    input mass [float];\n    input moment_of_inertia [float];\n    input center_of_mass [float];\n    input length [float];\n    input slave_throw [float];\n    alias output __out [connecting_rod_channel];\n}\n\npublic node connecting_rod {\n    input params: connecting_rod_parameters();\n    alias output __out [_connecting_rod]:\n        _connecting_rod(\n            mass: params.mass,\n            moment_of_inertia: params.moment_of_inertia,\n            center_of_mass: params.center_of_mass,\n            length: params.length,\n            slave_throw: params.slave_throw\n        );\n}\n\n// Piston\npublic node piston_parameters {\n    input blowby: 0.0;\n    input compression_height: 0.0;\n    input wrist_pin_position: 0.0;\n    input wrist_pin_location: 0.0;\n    input displacement: 0.0;\n    input mass: 0.0;\n}\n\nprivate node _piston => __engine_sim__piston {\n    input mass [float]: 0.0;\n    input blowby [float]: 0.0;\n    input compression_height [float]: 0.0;\n    input wrist_pin_position [float]: 0.0;\n    input displacement [float]: 0.0;\n    alias output __out [piston_channel];\n}\n\npublic node piston {\n    input params: piston_parameters();\n    input blowby: params.blowby;\n    alias output __out [_piston]:\n        _piston(\n            mass: params.mass,\n            blowby: blowby,\n            compression_height: params.compression_height,\n            wrist_pin_position: params.wrist_pin_position,\n            displacement: params.displacement\n        );\n}\n\n// Cylinder Bank\npublic node cylinder_bank_parameters {\n    input angle: 0.0;\n    input bore: 0.0;\n    input deck_height: 0.0;\n    input position_x: 0.0;\n    input position_y: 0.0;\n    input display_depth: 0.5;\n}\n\nprivate node _cylinder_bank => __engine_sim__cylinder_bank {\n    input angle [float]: 0.0;\n    input bore [float]: 0.0;\n    input deck_height [float]: 0.0;\n    input position_x [float]: 0.0;\n    input position_y [float]: 0.0;\n    input display_depth [float]: 0.6;\n    alias output __out [cylinder_bank_channel];\n}\n\npublic node cylinder_bank {\n    input parameters: cylinder_bank_parameters();\n    input angle: parameters.angle;\n    input bore: parameters.bore;\n    input deck_height: parameters.deck_height;\n    input position_x: parameters.position_x;\n    input position_y: parameters.position_y;\n    input display_depth: parameters.display_depth;\n    alias output __out [_cylinder_bank]:\n        _cylinder_bank(\n            angle: angle,\n            bore: bore,\n            deck_height: deck_height,\n            position_x: position_x,\n            position_y: position_y,\n            display_depth: display_depth\n        );\n}\n\n// Function\npublic node function => __engine_sim__function {\n    input filter_radius [float]: 1.0;\n    alias output __out [function_channel];\n}\n\n// Cylinder\npublic node cylinder_friction_parameter_defaults {\n    output friction_k: 0.06;\n    output breakaway_friction: 0.0;\n    output breakaway_friction_velocity: 0.0;\n    output viscous_friction_coefficient: 0.0;\n}\n\npublic node cylinder_friction_parameters {\n    input copy: cylinder_friction_parameter_defaults();\n    input friction_k: copy.friction_k;\n    input breakaway_friction: copy.breakaway_friction;\n    input breakaway_friction_velocity: copy.breakaway_friction_velocity;\n    input viscous_friction_coefficient: copy.viscous_friction_coefficient;\n}\n\n// Valvetrain\npublic node standard_valvetrain => __engine_sim__standard_valvetrain {\n    input intake_camshaft [camshaft];\n    input exhaust_camshaft [camshaft];\n    alias output __out [valvetrain_channel];\n}\n\npublic node vtec_valvetrain => __engine_sim__vtec_valvetrain {\n    input vtec_intake_camshaft [camshaft];\n    input vtec_exhaust_camshaft [camshaft];\n    input intake_camshaft [camshaft];\n    input exhaust_camshaft [camshaft];\n\n    input min_rpm [float]: 5800 * units.rpm;\n    input min_speed [float]: 10 * units.mph;\n    input manifold_vacuum [float]: 1.0 * units.atm - 5.0 * units.inHg;\n    input min_throttle_position [float]: 0.3;\n\n    alias output __out [valvetrain_channel];\n}\n\n// Cylinder Head\npublic node cylinder_head_parameters {\n    input intake_port_flow: function();\n    input exhaust_port_flow: function();\n    input chamber_volume: 118.0 * units.cc;\n\n    input intake_runner_volume: 300.0 * units.cc;\n    input intake_runner_cross_section_area: circle_area(0.75 * units.inch);\n\n    input exhaust_runner_volume: 300.0 * units.cc;\n    input exhaust_runner_cross_section_area: circle_area(0.85 * units.inch);\n\n    input flip_display: false;\n}\n\nprivate node _cylinder_head => __engine_sim__cylinder_head {\n    input intake_port_flow [function];\n    input exhaust_port_flow [function];\n    input valvetrain [valvetrain_channel];\n    input chamber_volume [float];\n    input intake_runner_volume [float];\n    input intake_runner_cross_section_area [float];\n    input exhaust_runner_volume [float];\n    input exhaust_runner_cross_section_area [float];\n    input flip_display [bool];\n    alias output __out [cylinder_head_channel];\n}\n\npublic node generic_cylinder_head {\n    input parameters: cylinder_head_parameters();\n    input valvetrain;\n\n    input intake_port_flow: parameters.intake_port_flow;\n    input exhaust_port_flow: parameters.exhaust_port_flow;\n\n    input chamber_volume: parameters.chamber_volume;\n\n    input intake_runner_volume: parameters.intake_runner_volume;\n    input intake_runner_cross_section_area: parameters.intake_runner_cross_section_area;\n\n    input exhaust_runner_volume: parameters.exhaust_runner_volume;\n    input exhaust_runner_cross_section_area: parameters.exhaust_runner_cross_section_area;\n\n    input flip_display: parameters.flip_display;\n\n    alias output __out [_cylinder_head]:\n        _cylinder_head(\n            intake_port_flow: intake_port_flow,\n            exhaust_port_flow: exhaust_port_flow,\n            chamber_volume: chamber_volume,\n            intake_runner_volume: intake_runner_volume,\n            intake_runner_cross_section_area: intake_runner_cross_section_area,\n            exhaust_runner_volume: exhaust_runner_volume,\n            exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n            flip_display: flip_display,\n            valvetrain: valvetrain\n        );\n}\n\npublic node cylinder_head {\n    input parameters: cylinder_head_parameters();\n\n    input intake_camshaft;\n    input exhaust_camshaft;\n\n    input intake_port_flow: parameters.intake_port_flow;\n    input exhaust_port_flow: parameters.exhaust_port_flow;\n\n    input chamber_volume: parameters.chamber_volume;\n\n    input intake_runner_volume: parameters.intake_runner_volume;\n    input intake_runner_cross_section_area: parameters.intake_runner_cross_section_area;\n\n    input exhaust_runner_volume: parameters.exhaust_runner_volume;\n    input exhaust_runner_cross_section_area: parameters.exhaust_runner_cross_section_area;\n\n    input flip_display: parameters.flip_display;\n\n    alias output __out [_cylinder_head]:\n        generic_cylinder_head(\n            intake_port_flow: intake_port_flow,\n            exhaust_port_flow: exhaust_port_flow,\n            chamber_volume: chamber_volume,\n            intake_runner_volume: intake_runner_volume,\n            intake_runner_cross_section_area: intake_runner_cross_section_area,\n            exhaust_runner_volume: exhaust_runner_volume,\n            exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n            flip_display: flip_display,\n            valvetrain: standard_valvetrain(\n                intake_camshaft: intake_camshaft,\n                exhaust_camshaft: exhaust_camshaft\n            )\n        );\n}\n\n// Camshaft\npublic node camshaft_parameters {\n    input advance: 0.0;\n    input base_radius: 0.0;\n    input lobe_profile: function();\n}\n\nprivate node _camshaft => __engine_sim__camshaft {\n    input advance [float];\n    input base_radius [float];\n    input lobe_profile [function];\n    alias output __out [camshaft_channel];\n}\n\npublic node camshaft {\n    input parameters: camshaft_parameters();\n    input advance: parameters.advance;\n    input base_radius: parameters.base_radius;\n    input lobe_profile: parameters.lobe_profile;\n    alias output __out [_camshaft]:\n        _camshaft(\n            advance: advance,\n            base_radius: base_radius,\n            lobe_profile: lobe_profile\n        );\n}\n\n// Intake\npublic node intake_parameters {\n    input plenum_volume: 2.0 * units.L;\n    input plenum_cross_section_area: 100.0 * units.cm2;\n    input intake_flow_rate: 0.0;\n    input idle_flow_rate: 0.0;\n    input molecular_afr: (25.0 / 2.0);\n    input idle_throttle_plate_position: 0.975;\n    input throttle_gamma: 2.0;\n    input runner_length: 4.0 * units.inch;\n    input runner_flow_rate: k_carb(200.0);\n    input velocity_decay: 0.25;\n}\n\nprivate node _intake => __engine_sim__intake {\n    input plenum_volume [float];\n    input plenum_cross_section_area [float];\n    input intake_flow_rate [float];\n    input idle_flow_rate [float];\n    input runner_flow_rate [float];\n    input molecular_afr [float];\n    input idle_throttle_plate_position [float];\n    input throttle_gamma [float];\n    input runner_length [float];\n    input velocity_decay [float];\n    alias output __out [intake_channel];\n}\n\npublic node intake {\n    input parameters: intake_parameters();\n    input plenum_volume: parameters.plenum_volume;\n    input plenum_cross_section_area: parameters.plenum_cross_section_area;\n    input intake_flow_rate: parameters.intake_flow_rate;\n    input idle_flow_rate: parameters.idle_flow_rate;\n    input runner_flow_rate: parameters.runner_flow_rate;\n    input molecular_afr: parameters.molecular_afr;\n    input idle_throttle_plate_position: parameters.idle_throttle_plate_position;\n    input throttle_gamma: parameters.throttle_gamma;\n    input runner_length: parameters.runner_length;\n    input velocity_decay: parameters.velocity_decay;\n    alias output __out [_intake]:\n        _intake(\n            plenum_volume: plenum_volume,\n            plenum_cross_section_area: plenum_cross_section_area,\n            intake_flow_rate: intake_flow_rate,\n            idle_flow_rate: idle_flow_rate,\n            runner_flow_rate: runner_flow_rate,\n            molecular_afr: molecular_afr,\n            idle_throttle_plate_position: idle_throttle_plate_position,\n            throttle_gamma: throttle_gamma,\n            runner_length: runner_length,\n            velocity_decay: velocity_decay\n        );\n}\n\n// Exhaust System\npublic node impulse_response => __engine_sim__impulse_response {\n    input filename [string];\n    input volume [float]: 1.0;\n    alias output __out [impulse_response_channel];\n}\n\npublic node exhaust_system_parameters {\n    input volume: 100.0 * units.L;\n    input length: volume / collector_cross_section_area;\n    input collector_cross_section_area: circle_area(2.0 * units.inch);\n    input outlet_flow_rate: k_carb(1000.0);\n    input primary_tube_length: 10.0 * units.inch;\n    input primary_flow_rate: k_carb(100.0);\n    input audio_volume: 1.0;\n    input velocity_decay: 1.0;\n}\n\nprivate node _exhaust_system => __engine_sim__exhaust_system {\n    input length [float];\n    input collector_cross_section_area [float];\n    input outlet_flow_rate [float];\n    input primary_tube_length [float];\n    input primary_flow_rate [float];\n    input audio_volume [float];\n    input velocity_decay [float];\n    input impulse_response [impulse_response];\n    alias output __out [exhaust_system_channel];\n}\n\npublic node exhaust_system {\n    input parameters: exhaust_system_parameters();\n    input length: parameters.length;\n    input collector_cross_section_area: parameters.collector_cross_section_area;\n    input outlet_flow_rate: parameters.outlet_flow_rate;   \n    input primary_tube_length: parameters.primary_tube_length;\n    input primary_flow_rate: parameters.primary_flow_rate;\n    input audio_volume: parameters.audio_volume;\n    input velocity_decay: parameters.velocity_decay;\n    input impulse_response;\n    alias output __out [_exhaust_system]:\n        _exhaust_system(\n            length: length,\n            collector_cross_section_area: collector_cross_section_area,\n            outlet_flow_rate: outlet_flow_rate,\n            primary_tube_length: primary_tube_length,\n            primary_flow_rate: primary_flow_rate,\n            audio_volume: audio_volume,\n            velocity_decay: velocity_decay,\n            impulse_response: impulse_response\n        );\n}\n\n// Ignition Module\npublic node ignition_module => __engine_sim__ignition_module {\n    input timing_curve [function];\n    input rev_limit [float]: 7000.0 * units.rpm;\n    input limiter_duration [float]: 0.5 * units.sec;\n    alias output __out [ignition_module_channel];\n}\n\npublic node ignition_wire => __engine_sim__ignition_wire {\n    alias output __out [ignition_wire_channel];\n}\n\npublic node vehicle => __engine_sim__vehicle {\n    input mass [float]: 1000 * units.kg;\n    input drag_coefficient [float]: 0.25;\n    input cross_sectional_area [float]: (72 * units.inch) * (72 * units.inch);\n    input diff_ratio [float]: 3.42;\n    input tire_radius [float]: 10 * units.inch;\n    input rolling_resistance [float]: 2000;\n\n    alias output __out [vehicle_channel];\n}\n\npublic node transmission => __engine_sim__transmission {\n    input max_clutch_torque [float]: 1000 * units.lb_ft;\n    alias output __out [transmission_channel];\n}\n"
  },
  {
    "path": "es/part-library/part_library.mr",
    "content": "public import \"parts/cam_lobes.mr\"\npublic import \"parts/camshafts.mr\"\npublic import \"parts/heads.mr\"\npublic import \"parts/intakes.mr\"\npublic import \"parts/ignition_modules.mr\"\n"
  },
  {
    "path": "es/part-library/parts/cam_lobes.mr",
    "content": "private import \"engine_sim.mr\"\n\nunits units()\n\nprivate node add_sym_sample {\n    input angle;\n    input lift;\n    input this;\n    alias output __out: this;\n\n    this.add_sample(angle * units.deg, lift * units.thou)\n    this.add_sample(-angle * units.deg, lift * units.thou)\n}\n\npublic node stock_454_intake_lobe_profile {\n    alias output __out:\n        harmonic_cam_lobe(\n            duration_at_50_thou: 194 * units.deg,\n            gamma: 0.8,\n            lift: 390 * units.thou,\n            steps: 100\n        );\n}\n\npublic node stock_454_exhaust_lobe_profile {\n    alias output __out:\n        harmonic_cam_lobe(\n            duration_at_50_thou: 202 * units.deg,\n            gamma: 0.8,\n            lift: 409 * units.thou,\n            steps: 100\n        );\n}\n\npublic node comp_cams_magnum_11_450_8_lobe_profile {\n    alias output __out:\n        harmonic_cam_lobe(\n            duration_at_50_thou: 232 * units.deg,\n            gamma: 0.75,\n            lift: 578 * units.thou,\n            steps: 100\n        );\n}\n\npublic node comp_cams_magnum_11_470_8_lobe_profile {\n    alias output __out:\n        harmonic_cam_lobe(\n            duration_at_50_thou: 252 * units.deg,\n            gamma: 0.8,\n            lift: 612 * units.thou,\n            steps: 100\n        );\n}\n"
  },
  {
    "path": "es/part-library/parts/camshafts.mr",
    "content": "private import \"cam_lobes.mr\"\n\nprivate import \"engine_sim.mr\"\n\nunits units()\n\npublic node chevy_bbc_camshaft_builder {\n    input lobe_profile: stock_454_intake_lobe_profile();\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114.0 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;\n    input advance: 0.0 * units.deg;\n    input base_radius: 0.75 * units.inch;\n\n    output intake_cam_0: _intake_cam_0;\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_0: _exhaust_cam_0;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params(\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot90(90 * units.deg)\n    label rot360(360 * units.deg)\n\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center)\n        .add_lobe(rot360 - exhaust_lobe_center + 3 * rot90)\n        .add_lobe(rot360 - exhaust_lobe_center + 5 * rot90)\n        .add_lobe(rot360 - exhaust_lobe_center + 6 * rot90)\n\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + 7 * rot90)\n        .add_lobe(rot360 - exhaust_lobe_center + 2 * rot90)\n        .add_lobe(rot360 - exhaust_lobe_center + 4 * rot90)\n        .add_lobe(rot360 - exhaust_lobe_center + 1 * rot90)\n\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center)\n        .add_lobe(rot360 + intake_lobe_center + 3 * rot90)\n        .add_lobe(rot360 + intake_lobe_center + 5 * rot90)\n        .add_lobe(rot360 + intake_lobe_center + 6 * rot90)\n\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + 7 * rot90)\n        .add_lobe(rot360 + intake_lobe_center + 2 * rot90)\n        .add_lobe(rot360 + intake_lobe_center + 4 * rot90)\n        .add_lobe(rot360 + intake_lobe_center + 1 * rot90)\n}\n\npublic node vtwin_camshaft_builder {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114.0 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;\n    input advance: 0.0 * units.deg;\n    input base_radius: 0.75 * units.inch;\n    input angle: 90 * 3 * units.deg;\n\n    output intake_cam_0: _intake_cam_0;\n    output intake_cam_1: _intake_cam_1;\n    output exhaust_cam_0: _exhaust_cam_0;\n    output exhaust_cam_1: _exhaust_cam_1;\n\n    camshaft_parameters params(\n        advance: advance,\n        base_radius: base_radius\n    )\n\n    camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile)\n    camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile)\n    camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile)\n    camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile)\n\n    label rot90(90 * units.deg)\n    label rot360(360 * units.deg)\n\n    _exhaust_cam_0\n        .add_lobe(rot360 - exhaust_lobe_center)\n    _exhaust_cam_1\n        .add_lobe(rot360 - exhaust_lobe_center + angle)\n    _intake_cam_0\n        .add_lobe(rot360 + intake_lobe_center)\n    _intake_cam_1\n        .add_lobe(rot360 + intake_lobe_center + angle)\n}\n\npublic node vtwin90_camshaft_builder {\n    input lobe_profile;\n    input intake_lobe_profile: lobe_profile;\n    input exhaust_lobe_profile: lobe_profile;\n    input lobe_separation: 114.0 * units.deg;\n    input intake_lobe_center: lobe_separation;\n    input exhaust_lobe_center: lobe_separation;\n    input advance: 0.0 * units.deg;\n    input base_radius: 0.75 * units.inch;\n\n    output intake_cam_0: camshaft.intake_cam_0;\n    output intake_cam_1: camshaft.intake_cam_1;\n    output exhaust_cam_0: camshaft.exhaust_cam_0;\n    output exhaust_cam_1: camshaft.exhaust_cam_1;\n\n    vtwin_camshaft_builder camshaft(\n        lobe_profile: lobe_profile,\n        intake_lobe_profile: intake_lobe_profile,\n        exhaust_lobe_profile: exhaust_lobe_profile,\n        lobe_separation: lobe_separation,\n        intake_lobe_center: intake_lobe_center,\n        exhaust_lobe_center: exhaust_lobe_center,\n        advance: advance,\n        base_radius: base_radius,\n        angle: 90 * 3 * units.deg\n    )\n}\n\npublic node chevy_454_stock_camshaft {\n    alias output __out:\n        chevy_bbc_camshaft_builder(\n            advance: 0 * units.deg,\n            intake_lobe_profile: stock_454_intake_lobe_profile(),\n            exhaust_lobe_profile: stock_454_exhaust_lobe_profile(),\n            intake_lobe_center: 108 * units.deg,\n            exhaust_lobe_center: 113 * units.deg);\n}\n\npublic node comp_cams_magnum_11_450_8 {\n    alias output __out:\n        chevy_bbc_camshaft_builder(\n            lobe_profile: comp_cams_magnum_11_450_8_lobe_profile(),\n            lobe_separation: 110 * units.deg,\n            advance: 4.0 * units.deg,\n            base_radius: 1000.0 * units.thou);\n}\n\npublic node comp_cams_magnum_11_470_8 {\n    alias output __out:\n        chevy_bbc_camshaft_builder(\n            lobe_profile: comp_cams_magnum_11_470_8_lobe_profile(),\n            lobe_separation: 110 * units.deg,\n            advance: 4.0 * units.deg,\n            base_radius: 1000.0 * units.thou);\n}\n"
  },
  {
    "path": "es/part-library/parts/heads.mr",
    "content": "private import \"engine_sim.mr\"\n\nunits units()\n\npublic node add_flow_sample {\n    input lift;\n    input flow;\n    input this;\n    alias output __out: this;\n\n    this.add_sample(lift * units.thou, k_28inH2O(flow))\n}\n\npublic node chevy_bbc_peanut_port_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0, 0)\n        .add_flow_sample(50, 25)\n        .add_flow_sample(100, 75)\n        .add_flow_sample(150, 100)\n        .add_flow_sample(200, 130)\n        .add_flow_sample(250, 180)\n        .add_flow_sample(300, 190)\n        .add_flow_sample(350, 220)\n        .add_flow_sample(400, 240)\n        .add_flow_sample(450, 250)\n        .add_flow_sample(500, 260)\n        .add_flow_sample(550, 260)\n        .add_flow_sample(600, 260)\n        .add_flow_sample(650, 255)\n        .add_flow_sample(700, 250)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0, 0)\n        .add_flow_sample(50, 25)\n        .add_flow_sample(100, 50)\n        .add_flow_sample(150, 75)\n        .add_flow_sample(200, 100)\n        .add_flow_sample(250, 125)\n        .add_flow_sample(300, 160)\n        .add_flow_sample(350, 175)\n        .add_flow_sample(400, 180)\n        .add_flow_sample(450, 190)\n        .add_flow_sample(500, 200)\n        .add_flow_sample(550, 205)\n        .add_flow_sample(600, 210)\n        .add_flow_sample(650, 210)\n        .add_flow_sample(700, 210)\n\n    cylinder_head head(\n        chamber_volume: 118.0 * units.cc,\n        intake_runner_volume: 189.0 * units.cc,\n        intake_runner_cross_section_area: 37.8 * units.cm2,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        intake_camshaft: intake_camshaft,\n        exhaust_camshaft: exhaust_camshaft,\n        flip_display: flip_display\n    )\n}\n\npublic node edelbrock_6055_rectangle_port {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0, 0)\n        .add_flow_sample(50, 25)\n        .add_flow_sample(100, 76)\n        .add_flow_sample(150, 100)\n        .add_flow_sample(200, 146)\n        .add_flow_sample(250, 175)\n        .add_flow_sample(300, 212)\n        .add_flow_sample(350, 230)\n        .add_flow_sample(400, 255)\n        .add_flow_sample(450, 275)\n        .add_flow_sample(500, 294)\n        .add_flow_sample(550, 300)\n        .add_flow_sample(600, 314)\n        .add_flow_sample(650, 314)\n        .add_flow_sample(700, 314)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0, 0)\n        .add_flow_sample(50, 25)\n        .add_flow_sample(100, 70)\n        .add_flow_sample(150, 100)\n        .add_flow_sample(200, 132)\n        .add_flow_sample(250, 140)\n        .add_flow_sample(300, 156)\n        .add_flow_sample(350, 170)\n        .add_flow_sample(400, 181)\n        .add_flow_sample(450, 191)\n        .add_flow_sample(500, 207)\n        .add_flow_sample(550, 214)\n        .add_flow_sample(600, 228)\n        .add_flow_sample(650, 228)\n        .add_flow_sample(700, 228)\n\n    cylinder_head head(\n        chamber_volume: 118.0 * units.cc,\n        intake_runner_volume: 315.0 * units.cc,\n        intake_runner_cross_section_area: 78.75 * units.cm2,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        intake_camshaft: intake_camshaft,\n        exhaust_camshaft: exhaust_camshaft,\n        flip_display: flip_display\n    )\n}\n\npublic node generic_small_engine_head {\n    input intake_camshaft;\n    input exhaust_camshaft;\n    input chamber_volume: 100.0 * units.cc;\n    input intake_runner_volume: 100.0 * units.cc;\n    input intake_runner_cross_section_area: 30.0 * units.cm2;\n    input exhaust_runner_volume: 100.0 * units.cc;\n    input exhaust_runner_cross_section_area: 30.0 * units.cm2;\n\n    input flow_attenuation: 1.0;\n    input lift_scale: 1.0;\n    input flip_display: false;\n    alias output __out: head;\n\n    function intake_flow(50 * units.thou)\n    intake_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 25 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 75 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 100 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 130 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 180 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 190 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 220 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 240 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 250 * flow_attenuation)\n        .add_flow_sample(500 * lift_scale, 260 * flow_attenuation)\n        .add_flow_sample(550 * lift_scale, 260 * flow_attenuation)\n        .add_flow_sample(600 * lift_scale, 260 * flow_attenuation)\n        .add_flow_sample(650 * lift_scale, 255 * flow_attenuation)\n        .add_flow_sample(700 * lift_scale, 250 * flow_attenuation)\n\n    function exhaust_flow(50 * units.thou)\n    exhaust_flow\n        .add_flow_sample(0 * lift_scale, 0 * flow_attenuation)\n        .add_flow_sample(50 * lift_scale, 25 * flow_attenuation)\n        .add_flow_sample(100 * lift_scale, 50 * flow_attenuation)\n        .add_flow_sample(150 * lift_scale, 75 * flow_attenuation)\n        .add_flow_sample(200 * lift_scale, 100 * flow_attenuation)\n        .add_flow_sample(250 * lift_scale, 125 * flow_attenuation)\n        .add_flow_sample(300 * lift_scale, 160 * flow_attenuation)\n        .add_flow_sample(350 * lift_scale, 175 * flow_attenuation)\n        .add_flow_sample(400 * lift_scale, 180 * flow_attenuation)\n        .add_flow_sample(450 * lift_scale, 190 * flow_attenuation)\n        .add_flow_sample(500 * lift_scale, 200 * flow_attenuation)\n        .add_flow_sample(550 * lift_scale, 205 * flow_attenuation)\n        .add_flow_sample(600 * lift_scale, 210 * flow_attenuation)\n        .add_flow_sample(650 * lift_scale, 210 * flow_attenuation)\n        .add_flow_sample(700 * lift_scale, 210 * flow_attenuation)\n\n    cylinder_head head(\n        chamber_volume: chamber_volume,\n        intake_runner_volume: intake_runner_volume,\n        intake_runner_cross_section_area: intake_runner_cross_section_area,\n        exhaust_runner_volume: exhaust_runner_volume,\n        exhaust_runner_cross_section_area: exhaust_runner_cross_section_area,\n\n        intake_port_flow: intake_flow,\n        exhaust_port_flow: exhaust_flow,\n        intake_camshaft: intake_camshaft,\n        exhaust_camshaft: exhaust_camshaft,\n        flip_display: flip_display\n    )\n}\n"
  },
  {
    "path": "es/part-library/parts/ignition_modules.mr",
    "content": "private import \"engine_sim.mr\"\n\nunits units()\nlabel cycle(2 * 360 * units.deg)\n\npublic node chevy_bbc_distributor {\n    input wires;\n    input timing_curve;\n    input rev_limit: 5500 * units.rpm;\n    alias output __out:\n        ignition_module(timing_curve: timing_curve, rev_limit: rev_limit)\n            .connect_wire(wires.wire1, (0.0 / 8.0) * cycle)\n            .connect_wire(wires.wire8, (1.0 / 8.0) * cycle)\n            .connect_wire(wires.wire4, (2.0 / 8.0) * cycle)\n            .connect_wire(wires.wire3, (3.0 / 8.0) * cycle)\n            .connect_wire(wires.wire6, (4.0 / 8.0) * cycle)\n            .connect_wire(wires.wire5, (5.0 / 8.0) * cycle)\n            .connect_wire(wires.wire7, (6.0 / 8.0) * cycle)\n            .connect_wire(wires.wire2, (7.0 / 8.0) * cycle);\n}\n\npublic node chevy_sbc_distributor {\n    input wires;\n    input timing_curve;\n    input rev_limit: 5500 * units.rpm;\n    alias output __out: chevy_bbc_distributor(\n        wires: wires,\n        timing_curve: timing_curve,\n        rev_limit: rev_limit\n    );\n}\n\npublic node vtwin90_distributor {\n    input wires;\n    input timing_curve;\n    input rev_limit: 5500 * units.rpm;\n    alias output __out:\n        ignition_module(timing_curve: timing_curve, rev_limit: rev_limit)\n            .connect_wire(wires.wire1, (0.0 / 8.0) * cycle)\n            .connect_wire(wires.wire2, (3.0 / 8.0) * cycle);\n}\n\npublic node single_cylinder_distributor {\n    input wires;\n    input timing_curve;\n    input rev_limit: 5500 * units.rpm;\n    alias output __out:\n        ignition_module(timing_curve: timing_curve, rev_limit: rev_limit)\n            .connect_wire(wires.wire1, (0.0 / 8.0) * cycle);\n}\n"
  },
  {
    "path": "es/part-library/parts/intakes.mr",
    "content": "private import \"engine_sim.mr\"\n\nunits units()\n\npublic node chevy_bbc_stock_intake {\n    input carburetor_cfm: 650.0;\n    input idle_flow_rate_cfm: 1.0;\n    input idle_throttle_plate_position: 0.975;\n    input throttle_gamma: 2.0;\n\n    alias output __out: intake;\n\n    intake intake(\n        plenum_volume: 2.0 * units.L,\n        plenum_cross_section_area: 100.0 * units.cm2,\n        intake_flow_rate: k_carb(carburetor_cfm),\n        idle_flow_rate: k_carb(idle_flow_rate_cfm),\n        idle_throttle_plate_position: idle_throttle_plate_position,\n        throttle_gamma: throttle_gamma,\n        runner_flow_rate: k_carb(300.0),\n        runner_length: 6.0 * units.inch,\n        velocity_decay: 1.0\n    )\n}\n\npublic node performer_rpm_intake {\n    input carburetor_cfm: 650.0;\n    input idle_flow_rate_cfm: 1.0;\n    input idle_throttle_plate_position: 0.975;\n    input throttle_gamma: 2.0;\n\n    alias output __out: intake;\n\n    intake intake(\n        plenum_volume: 2.0 * units.L,\n        plenum_cross_section_area: 100.0 * units.cm2,\n        intake_flow_rate: k_carb(carburetor_cfm),\n        idle_flow_rate: k_carb(idle_flow_rate_cfm),\n        idle_throttle_plate_position: idle_throttle_plate_position,\n        throttle_gamma: throttle_gamma,\n        runner_flow_rate: k_carb(500.0),\n        runner_length: 6.0 * units.inch,\n        velocity_decay: 0.1\n    )\n}\n"
  },
  {
    "path": "es/settings/application_settings.mr",
    "content": "private import \"engine_sim.mr\"\n\nunits units()\n\npublic node set_application_settings => __engine_sim__set_application_settings {\n    input start_fullscreen [bool]: false;\n\tinput power_units [string]: \"HP\";\n\tinput torque_units [string]: \"FTLBS\";\n\tinput speed_units [string]: \"MPH\";\n\tinput pressure_units [string]: \"INHG\";\n\tinput boost_units [string]: \"PSI\";\n\tinput color_background [int]: 0x0E1012;\n    input color_foreground [int]: 0xFFFFFF;\n    input color_shadow [int]: 0x0E1012;\n    input color_highlight1 [int]: 0xEF4545;\n    input color_highlight2 [int]: 0xFFFFFF;\n    input color_pink [int]: 0xF394BE;\n    input color_red [int]: 0xEE4445;\n    input color_orange [int]: 0xF4802A;\n    input color_yellow [int]: 0xFDBD2E;\n    input color_blue [int]: 0x77CEE0;\n    input color_green [int]: 0xBDD869;\n\n}\n"
  },
  {
    "path": "es/sound-library/impulse_responses.mr",
    "content": "private import \"engine_sim.mr\"\n\npublic node impulse_response_library {\n    output default_0: impulse_response(filename: \"smooth/smooth_39.wav\", volume: 0.001);\n\n    output real_engine_0:\n        impulse_response(filename: \"archive/test_engine_14_eq_adjusted_16.wav\", volume: 0.001);\n    output real_engine_1:\n        impulse_response(filename: \"archive/test_engine_15_eq_adjusted_16.wav\", volume: 0.001);\n    output real_engine_2:\n        impulse_response(filename: \"archive/test_engine_16_eq_adjusted_16.wav\", volume: 0.001);\n\n    output sharp_0:\n        impulse_response(filename: \"sharp/sharp_01.wav\", volume: 0.001);\n\n    output mild_exhaust_0:\n        impulse_response(filename: \"new/mild_exhaust.wav\", volume: 0.01);\n    output mild_exhaust_0_reverb:\n        impulse_response(filename: \"new/mild_exhaust_reverb.wav\", volume: 0.01);\n    output minimal_muffling_01:\n        impulse_response(filename: \"new/minimal_muffling_01.wav\", volume: 0.01);\n    output minimal_muffling_02:\n        impulse_response(filename: \"new/minimal_muffling_02.wav\", volume: 0.01);\n    output minimal_muffling_03:\n        impulse_response(filename: \"new/minimal_muffling_03.wav\", volume: 0.01);\n}\n"
  },
  {
    "path": "es/types/atomic_types.mr",
    "content": "module {\n    @name:      \"Atomic Types\"\n    @author:    \"ATG (Ange Yaghi)\"\n    @copyright: \"Copyright 2022, Ange Yaghi\"\n}\n\n// ========================================================\n//  Channels\n// ========================================================\n\n@doc: \"Floating-point channel type\"\nprivate node float_channel => __engine_sim__float { /* void */ }\n\n@doc: \"String channel type\"\nprivate node string_channel => __engine_sim__string { /* void */ }\n\n@doc: \"Integer channel type\"\nprivate node int_channel => __engine_sim__int { /* void */ }\n\n@doc: \"Bool channel type\"\nprivate node bool_channel => __engine_sim__bool { /* void */ }\n\n// ========================================================\n//  Types\n// ========================================================\n\n@doc: \"Float cast type\"\n@detail: \"Converts anything connected to __in to \"\n         \"a float type\"\npublic inline node float {\n    input __in [::float_channel]: 0.0;\n    alias output __out [::float_channel]: __in;\n}\n\n@doc: \"Integer cast type\"\n@detail: \"Converts anything connected to __in to \"\n         \"an integer type\"\npublic inline node int {\n    input __in [::int_channel]: 0;\n    alias output __out [::int_channel]: __in;\n}\n\n@doc: \"Boolean cast type\"\n@detail: \"Converts anything connected to __in to \"\n         \"a boolean type\"\npublic inline node bool {\n    input __in [::bool_channel]: false;\n    alias output __out [::bool_channel]: __in;\n}\n\n@doc: \"String type\"\npublic inline node string {\n    input s [::string_channel]: \"\";\n    alias output __out [::string_channel]: s;\n}\n\n// ========================================================\n//  Literals\n// ========================================================\n\npublic node literal_string => __engine_sim__literal_string {\n    alias output __out [::string];\n}\n\npublic node literal_float => __engine_sim__literal_float {\n    alias output __out [::float];\n}\n\npublic node literal_int => __engine_sim__literal_int {\n    alias output __out [::int];\n}\n\npublic node literal_bool => __engine_sim__literal_bool {\n    alias output __out [::bool];\n}\n"
  },
  {
    "path": "es/types/conversions.mr",
    "content": "module {\n    @name:      \"Conversions\"\n    @author:    \"ATG (Ange Yaghi)\"\n    @copyright: \"Copyright 2022, Ange Yaghi\"\n}\n\nprivate import \"atomic_types.mr\"\n\n// Float conversions\npublic node int_to_float => __engine_sim__int_to_float {\n    input __in [int];\n    alias output __out [float];\n}\n\n// String conversions\npublic node int_to_string => __engine_sim__int_to_string {\n    input __in [int];\n    alias output __out [string];\n}\n\n// Integer conversions\npublic node string_to_int => __engine_sim__string_to_int {\n    input __in [string];\n    alias output __out [int];\n}\n"
  },
  {
    "path": "es/types/operations.mr",
    "content": "module {\n    @name:      \"Operations\"\n    @author:    \"ATG (Ange Yaghi)\"\n    @copyright: \"Copyright 2022, Ange Yaghi\"\n}\n\nprivate import \"atomic_types.mr\"\nprivate import \"conversions.mr\"\n\n// Float operations\n\npublic node float_negate => __engine_sim__float_negate {\n    input __in [float];\n    alias output __out [float];\n}\n\npublic node float_divide => __engine_sim__float_divide {\n    input __in0 [float];\n    input __in1 [float];\n    alias output __out [float];\n}\n\npublic node float_multiply => __engine_sim__float_multiply {\n    input __in0 [float];\n    input __in1 [float];\n    alias output __out [float];\n}\n\npublic node float_add => __engine_sim__float_add {\n    input __in0 [float];\n    input __in1 [float];\n    alias output __out [float];\n}\n\npublic node float_subtract => __engine_sim__float_subtract {\n    input __in0 [float];\n    input __in1 [float];\n    alias output __out [float];\n}\n\n// String operations\n\npublic node string_add => __engine_sim__string_add {\n    input __in0 [string];\n    input __in1 [string];\n    alias output __out [string];\n}\n\n// Int operations\n\npublic node int_add => __engine_sim__int_add {\n    input __in0 [int];\n    input __in1 [int];\n    alias output __out [int];\n}\n\npublic node int_mul => __engine_sim__int_multiply {\n    input __in0 [int];\n    input __in1 [int];\n    alias output __out [int];\n}\n\npublic node int_sub => __engine_sim__int_subtract {\n    input __in0 [int];\n    input __in1 [int];\n    alias output __out [int];\n}\n\npublic node int_div => __engine_sim__int_divide {\n    input __in0 [int];\n    input __in1 [int];\n    alias output __out [int];\n}\n\npublic node int_negate => __engine_sim__int_negate {\n    input __in [int];\n    alias output __out [int];\n}\n"
  },
  {
    "path": "es/utilities/utilities.mr",
    "content": "private import \"engine_sim.mr\"\n\nnode rod_moment_of_inertia {\n    input mass;\n    input length;\n    alias output __moment:\n        (1 / 12.0) * mass * length * length;\n}\n\nnode disk_moment_of_inertia {\n    input mass;\n    input radius;\n    alias output __moment:\n        (1 / 2.0) * mass * radius * radius;\n}\n"
  },
  {
    "path": "include/adaptive_volume_filter.h",
    "content": "#ifndef ATG_ENGINE_SIM_ADAPTIVE_VOLUME_FILTER_H\n#define ATG_ENGINE_SIM_ADAPTIVE_VOLUME_FILTER_H\n\n#include \"filter.h\"\n\nclass AdaptiveVolumeFilter : public Filter {\n    public:\n        AdaptiveVolumeFilter();\n        virtual ~AdaptiveVolumeFilter();\n\n        void initialize(int lookahead);\n        virtual double f(double sample);\n        virtual void destroy();\n\n    protected:\n        double *m_samples;\n        int m_sampleCount;\n        int m_lookahead;\n};\n\n#endif /* ATG_ENGINE_SIM_CONVOLUTION_ADAPTIVE_VOLUME */\n"
  },
  {
    "path": "include/afr_cluster.h",
    "content": "#ifndef ATG_ENGINE_SIM_AFR_CLUSTER_H\n#define ATG_ENGINE_SIM_AFR_CLUSTER_H\n\n#include \"ui_element.h\"\n\n#include \"engine.h\"\n#include \"gauge.h\"\n#include \"cylinder_temperature_gauge.h\"\n#include \"cylinder_pressure_gauge.h\"\n#include \"labeled_gauge.h\"\n#include \"throttle_display.h\"\n\nclass AfrCluster : public UiElement {\n    public:\n        AfrCluster();\n        virtual ~AfrCluster();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n\n        Engine *m_engine;\n\n    protected:\n        LabeledGauge *m_intakeAfrGauge;\n        LabeledGauge *m_exhaustAfrGauge;\n};\n\n#endif /* ATG_ENGINE_SIM_AFR_CLUSTER_H */\n"
  },
  {
    "path": "include/application_settings.h",
    "content": "#ifndef ATG_ENGINE_SIM_APPLICATION_SETTINGS_H\n#define ATG_ENGINE_SIM_APPLICATION_SETTINGS_H\n#include <string>\n\nstruct ApplicationSettings {\n    bool startFullscreen = false;\n    std::string powerUnits = \"hp\";\n    std::string torqueUnits = \"lb-ft\";\n    std::string speedUnits = \"mph\";\n    std::string pressureUnits = \"inHg\";\n    std::string boostUnits = \"psi\";\n\n    int colorBackground = 0x0E1012;\n    int colorForeground = 0xFFFFFF;\n    int colorShadow = 0x0E1012;\n    int colorHighlight1 = 0xEF4545;\n    int colorHighlight2 = 0xFFFFFF;\n    int colorPink = 0xF394BE;\n    int colorRed = 0xEE4445;\n    int colorOrange = 0xF4802A;\n    int colorYellow = 0xFDBD2E;\n    int colorBlue = 0x77CEE0;\n    int colorGreen = 0xBDD869;\n};\n\n#endif /* ATG_ENGINE_SIM_APPLICATION_SETTINGS_H */\n"
  },
  {
    "path": "include/audio_buffer.h",
    "content": "#ifndef ATG_ENGINE_SIM_AUDIO_BUFFER_H\n#define ATG_ENGINE_SIM_AUDIO_BUFFER_H\n\n#include \"scs.h\"\n\n#include <stdint.h>\n\nclass AudioBuffer {\n    public:\n        AudioBuffer();\n        ~AudioBuffer();\n\n        void initialize(int sampleRate, int bufferSize);\n        void destroy();\n\n        inline double offsetToTime(int offset) const {\n            return offset * m_offsetToSeconds;\n        }\n\n        inline double timeDelta(int offset0, int offset1) const {\n            if (offset1 == offset0) return 0;\n            else if (offset1 < offset0) {\n                return offsetToTime((m_bufferSize - offset0) + offset1);\n            }\n            else {\n                return offsetToTime(offset1 - offset0);\n            }\n        }\n\n        inline int offsetDelta(int offset0, int offset1) const {\n            if (offset1 == offset0) return 0;\n            else if (offset1 < offset0) {\n                return (m_bufferSize - offset0) + offset1;\n            }\n            else {\n                return offset1 - offset0;\n            }\n        }\n\n        inline void writeSample(int16_t sample, int offset, int index = 0) {\n            m_samples[getBufferIndex(offset, index)] = sample;\n        }\n\n        inline int16_t readSample(int offset, int index) const {\n            return m_samples[getBufferIndex(offset, index)];\n        }\n\n        inline void commitBlock(int length) {\n            m_writePointer = getBufferIndex(m_writePointer, length);\n        }\n\n        inline int getBufferIndex(int offset, int index = 0) const {\n            return (((offset + index) % m_bufferSize) + m_bufferSize) % m_bufferSize;\n        }\n\n        inline void copyBuffer(int16_t *dest, int offset, int length) {\n            const int start = getBufferIndex(offset, 0);\n            const int end = getBufferIndex(offset, length);\n\n            if (start == end) return;\n            else if (start < end) {\n                memcpy(dest, m_samples + start, length * sizeof(int16_t));\n            }\n            else {\n                memcpy(dest, m_samples + start, ((size_t)m_bufferSize - start) * sizeof(int16_t));\n                memcpy(dest + m_bufferSize - start, m_samples, end * sizeof(int16_t));\n            }\n        }\n\n        bool checkForDiscontinuitiy(int threshold) const;\n\n        int m_writePointer;\n\n    protected:\n        int m_sampleRate;\n        int16_t *m_samples;\n        int m_bufferSize;\n\n        double m_offsetToSeconds;\n};\n\n#endif /* ATG_ENGINE_SIM_AUDIO_BUFFER_H */\n"
  },
  {
    "path": "include/butterworth_low_pass_filter.h",
    "content": "#ifndef ATG_ENGINE_SIM_BUTTERWORTH_LOW_PASS_FILTER_H\n#define ATG_ENGINE_SIM_BUTTERWORTH_LOW_PASS_FILTER_H\n\n#include \"filter.h\"\n\n#include \"constants.h\"\n#include \"ring_buffer.h\"\n\n#include <cmath>\n\ntemplate <typename T_Real>\nclass ButterworthLowPassFilter : public Filter {\npublic:\n    ButterworthLowPassFilter() {\n        m_y.initialize(4);\n        m_x.initialize(4);\n\n        for (int i = 0; i < 4; ++i) {\n            m_y.write(0);\n            m_x.write(0);\n        }\n    }\n\n    virtual ~ButterworthLowPassFilter() {\n        /* void */\n    }\n\n    __forceinline T_Real fast_f(T_Real sample) {\n        const T_Real y_prev[4] = {\n            m_y.read(3),\n            m_y.read(2),\n            m_y.read(1),\n            m_y.read(0)\n        };\n\n        const T_Real x_prev[4] = {\n            m_x.read(3),\n            m_x.read(2),\n            m_x.read(1),\n            m_x.read(0)\n        };\n\n        T_Real const n = m_f_4 / m_a[0] * (sample + 4 * x_prev[0] + 6 * x_prev[1] + 4 * x_prev[2] + x_prev[3]);\n        T_Real const d = -m_a[1] * y_prev[0] - m_a[2] * y_prev[1] - m_a[3] * y_prev[2] - m_a[4] * y_prev[3];\n        T_Real const y = n + d;\n\n        m_x.removeBeginning(1);\n        m_x.write(sample);\n\n        m_y.removeBeginning(1);\n        m_y.write(y);\n\n        return y;\n    }\n\n    inline void setCutoffFrequency(T_Real f_c, T_Real sampleRate) {\n        const T_Real f = std::tan(static_cast<T_Real>(constants::pi) * f_c / sampleRate);\n        const T_Real f_2 = f * f;\n        const T_Real f_3 = f_2 * f;\n        const T_Real f_4 = f_2 * f_2;\n        const T_Real m = -2.0 * std::cos(5.0 * constants::pi / 8.0);\n        const T_Real n = -2.0 * std::cos(7.0 * constants::pi / 8.0);\n\n        m_a[0] = 1.0 + (m + n) * f + (2.0 + n * m) * f_2 + (m + n) * f_3 + f_4;\n        m_a[1] = (-4.0 - 2.0 * (n + m) * f + 2.0 * (m + n) * f_3 + 4.0 * f_4) / m_a[0];\n        m_a[2] = (6.0 - 2.0 * (2.0 + m * n) * f_2 + 6.0 * f_4) / m_a[0];\n        m_a[3] = (-4.0 + 2.0 * (m + n) * f - 2.0 * (m + n) * f_3 + 4.0 * f_4) / m_a[0];\n        m_a[4] = (1.0 - (n + m) * f + (2.0 + m * n) * f_2 - (m + n) * f_3 + f_4) / m_a[0];\n\n        m_f_4 = f_4;\n    }\n\nprotected:\n    RingBuffer<T_Real> m_y;\n    RingBuffer<T_Real> m_x;\n    T_Real m_a[5];\n    T_Real m_f_4;\n};\n\n#endif /* ATG_ENGINE_SIM_BUTTERWORTH_LOW_PASS_FILTER_H */\n"
  },
  {
    "path": "include/camshaft.h",
    "content": "#ifndef ATG_ENGINE_SIM_CAMSHAFT_H\n#define ATG_ENGINE_SIM_CAMSHAFT_H\n\n#include \"part.h\"\n\n#include \"function.h\"\n#include \"units.h\"\n\nclass Crankshaft;\nclass Camshaft : public Part {\n    public:\n        struct Parameters {\n            // Number of lobes\n            int lobes;\n\n            // Camshaft advance in camshaft degrees\n            double advance = 0;\n\n            // Corresponding crankshaft\n            Crankshaft *crankshaft;\n\n            // Lobe profile\n            Function *lobeProfile;\n\n            // Base radius\n            double baseRadius = units::distance(600, units::thou);\n        };\n\n    public:\n        Camshaft();\n        virtual ~Camshaft();\n\n        void initialize(const Parameters &params);\n        virtual void destroy();\n\n        double valveLift(int lobe) const;\n        double sampleLobe(double theta) const;\n\n        void setLobeCenterline(int lobe, double crankAngle) { m_lobeAngles[lobe] = crankAngle / 2; }\n        double getLobeCenterline(int lobe) const { return m_lobeAngles[lobe]; }\n\n        double getAngle() const;\n\n        Function *getLobeProfile() const { return m_lobeProfile; }\n        double getAdvance() const { return m_advance; }\n        double getBaseRadius() const { return m_baseRadius; }\n\n    private:\n        Crankshaft *m_crankshaft;\n        Function *m_lobeProfile;\n        double *m_lobeAngles;\n        double m_advance;\n        double m_baseRadius;\n        int m_lobes;\n};\n\n#endif /* ATG_ENGINE_SIM_CAMSHAFT_H */\n"
  },
  {
    "path": "include/combustion_chamber.h",
    "content": "#ifndef ATG_ENGINE_SIM_COMBUSTION_CHAMBER_H\n#define ATG_ENGINE_SIM_COMBUSTION_CHAMBER_H\n\n#include \"scs.h\"\n\n#include \"piston.h\"\n#include \"gas_system.h\"\n#include \"cylinder_head.h\"\n#include \"units.h\"\n#include \"fuel.h\"\n\nclass Engine;\nclass CombustionChamber : public atg_scs::ForceGenerator {\n    public:\n        struct Parameters {\n            Piston *Piston;\n            CylinderHead *Head;\n            Fuel *Fuel;\n            Function *MeanPistonSpeedToTurbulence;\n\n            double StartingPressure;\n            double StartingTemperature;\n            double CrankcasePressure;\n        };\n\n        struct FlameEvent {\n            double lit_n = 0;\n            double total_n = 0;\n            double percentageLit = 0;\n            double efficiency = 1.0;\n            double flameSpeed = 0.0;\n\n            double lastVolume = 0.0;\n            double travel_x = 0.0;\n            double travel_y = 0.0;\n            GasSystem::Mix globalMix;\n        };\n\n        struct FrictionModelParams {\n            double frictionCoeff = 0.06;\n            double breakawayFriction = units::force(50, units::N);\n            double breakawayFrictionVelocity = units::distance(0.1, units::m);\n            double viscousFrictionCoefficient = units::force(20, units::N);\n        };\n\n    public:\n        CombustionChamber();\n        virtual ~CombustionChamber();\n\n        void initialize(const Parameters &params);\n        void destroy();\n        void setEngine(Engine *engine) { m_engine = engine; }\n        virtual void apply(atg_scs::SystemState *system);\n\n        CylinderHead *getCylinderHead() const { return m_head; }\n        Piston *getPiston() const { return m_piston; }\n\n        double getFrictionForce() const;\n        double getVolume() const;\n        double pistonSpeed() const;\n        double calculateMeanPistonSpeed() const;\n        double calculateFiringPressure() const;\n\n        bool isLit() const { return m_lit; }\n        bool popLitLastFrame();\n\n        void ignite();\n        void update(double dt);\n        void flow(double dt);\n\n        double lastEventAfr() const;\n\n        double getLastIterationExhaustFlow() const { return m_exhaustFlow; }\n\n        void resetLastTimestepExhaustFlow() { m_lastTimestepTotalExhaustFlow = 0; }\n        double getLastTimestepExhaustFlow() const { return m_lastTimestepTotalExhaustFlow; }\n\n        void resetLastTimestepIntakeFlow() { m_lastTimestepTotalIntakeFlow = 0; }\n        double getLastTimestepIntakeFlow() const { return m_lastTimestepTotalIntakeFlow; }\n\n        Function *m_meanPistonSpeedToTurbulence;\n        GasSystem m_system;\n        GasSystem m_intakeRunnerAndManifold;\n        GasSystem m_exhaustRunnerAndPrimary;\n        FlameEvent m_flameEvent;\n        bool m_lit;\n\n        FrictionModelParams m_frictionModel;\n\n        double m_peakTemperature;\n        double m_nBurntFuel;\n\n    protected:\n        double calculateFrictionForce(double v) const;\n        void updateCycleStates();\n\n        double m_intakeFlowRate;\n        double m_exhaustFlowRate;\n\n        double m_manifoldToRunnerFlowRate;\n        double m_primaryToCollectorFlowRate;\n        double m_cylinderCrossSectionSurfaceArea;\n        double m_cylinderWidthApproximation;\n\n        double m_lastTimestepTotalExhaustFlow;\n        double m_lastTimestepTotalIntakeFlow;\n        double m_exhaustFlow;\n\n        double m_crankcasePressure;\n\n        double *m_pressure;\n        double *m_pistonSpeed;\n        static constexpr int StateSamples = 256;\n\n        bool m_litLastFrame;\n\n        Piston *m_piston;\n        CylinderHead *m_head;\n        Engine *m_engine;\n        Fuel *m_fuel;\n};\n\n#endif /* ATG_ENGINE_SIM_COMBUSTION_CHAMBER_H */\n"
  },
  {
    "path": "include/combustion_chamber_object.h",
    "content": "#ifndef ATG_ENGINE_SIM_COMBUSTION_CHAMBER_OBJECT_H\n#define ATG_ENGINE_SIM_COMBUSTION_CHAMBER_OBJECT_H\n\n#include \"simulation_object.h\"\n\n#include \"combustion_chamber.h\"\n#include \"geometry_generator.h\"\n\nclass CombustionChamberObject : public SimulationObject {\n    public:\n        CombustionChamberObject();\n        virtual ~CombustionChamberObject();\n\n        virtual void generateGeometry();\n        virtual void render(const ViewParameters *view);\n        virtual void process(float dt);\n        virtual void destroy();\n\n        CombustionChamber *m_chamber;\n\n    protected:\n        GeometryGenerator::GeometryIndices m_indices;\n};\n\n#endif /* ATG_ENGINE_SIM_COMBUSTION_CHAMBER_OBJECT_H */\n"
  },
  {
    "path": "include/connecting_rod.h",
    "content": "#ifndef ATG_ENGINE_CONNECTING_ROD_H\n#define ATG_ENGINE_CONNECTING_ROD_H\n\n#include \"part.h\"\n\n#include \"crankshaft.h\"\n\nclass Piston;\nclass ConnectingRod : public Part {\n    public:\n        struct Parameters {\n            double mass = 0.0;\n            double momentOfInertia = 0.0;\n            double centerOfMass = 0.0;\n            double length = 0.0;\n\n            int rodJournals = 0;\n            double slaveThrow = 0;\n\n            Piston *piston = nullptr;\n\n            Crankshaft *crankshaft = nullptr;\n            ConnectingRod *master = nullptr;\n            int journal = 0;\n        };\n\n    public:\n        ConnectingRod();\n        virtual ~ConnectingRod();\n\n        void initialize(const Parameters &params);\n\n        double getBigEndLocal() const;\n        double getLittleEndLocal() const;\n\n        void setMaster(ConnectingRod *rod) { m_master = rod; }\n        void setCrankshaft(Crankshaft *crank) { m_crankshaft = crank; }\n\n        inline int getRodJournalCount() const { return m_rodJournalCount; }\n        void setRodJournalAngle(int i, double angle);\n        void getRodJournalPositionLocal(int i, double *x, double *y);\n        void getRodJournalPositionGlobal(int i, double *x, double *y);\n        double getRodJournalAngle(int i) { return m_rodJournalAngles[i]; }\n\n        inline double getSlaveThrow() const { return m_slaveThrow; }\n        inline double getCenterOfMass() const { return m_centerOfMass; }\n        inline double getLength() const { return m_length; }\n        inline double getMass() const { return m_m; }\n        inline double getMomentOfInertia() const { return m_I; }\n        inline int getJournal() const { return m_journal; }\n        int getLayer() const;\n        inline ConnectingRod *getMasterRod() const { return m_master; }\n        inline Crankshaft *getCrankshaft() const { return m_crankshaft; }\n        inline Piston *getPiston() const { return m_piston; }\n\n    protected:\n        double m_centerOfMass;\n        double m_length;\n        double m_m;\n        double m_I;\n        int m_journal;\n        ConnectingRod *m_master;\n        Crankshaft *m_crankshaft;\n        Piston *m_piston;\n\n        double m_slaveThrow;\n        double *m_rodJournalAngles;\n        int m_rodJournalCount;\n};\n\n#endif /* ATG_ENGINE_SIM_CONNECTING_ROD_H */\n"
  },
  {
    "path": "include/connecting_rod_object.h",
    "content": "#ifndef ATG_ENGINE_SIM_CONNECTING_ROD_OBJECT_H\n#define ATG_ENGINE_SIM_CONNECTING_ROD_OBJECT_H\n\n#include \"simulation_object.h\"\n\n#include \"connecting_rod.h\"\n#include \"geometry_generator.h\"\n\nclass ConnectingRodObject : public SimulationObject {\n    public:\n        ConnectingRodObject();\n        virtual ~ConnectingRodObject();\n\n        virtual void generateGeometry();\n        virtual void render(const ViewParameters *view);\n        virtual void process(float dt);\n        virtual void destroy();\n\n        ConnectingRod *m_connectingRod;\n\n    protected:\n        GeometryGenerator::GeometryIndices\n            m_connectingRodBody,\n            m_pins;\n};\n\n#endif /* ATG_ENGINE_SIM_CONNECTING_ROD_OBJECT_H */\n"
  },
  {
    "path": "include/constants.h",
    "content": "#ifndef ATG_ENGINE_SIM_CONSTANTS_H\n#define ATG_ENGINE_SIM_CONSTANTS_H\n\nnamespace constants {\n\n    extern constexpr double pi = 3.14159265359;\n    extern constexpr double R = 8.31446261815324;\n    extern constexpr double root_2 = 1.41421356237309504880168872420969807856967187537694807317667973799;\n    extern constexpr double e = 2.71828182845904523536028747135266249775724709369995;\n\n} /* namespace Constants */\n\n#endif /* ATG_ENGINE_SIM_CONSTANTS_H */\n"
  },
  {
    "path": "include/convolution_filter.h",
    "content": "#ifndef ATG_ENGINE_SIM_CONVOLUTION_FILTER_H\n#define ATG_ENGINE_SIM_CONVOLUTION_FILTER_H\n\n#include \"filter.h\"\n\nclass ConvolutionFilter : public Filter {\n    public:\n        ConvolutionFilter();\n        virtual ~ConvolutionFilter();\n\n        void initialize(int samples);\n        virtual float f(float sample) override;\n        virtual void destroy();\n\n        int getSampleCount() const { return m_sampleCount; }\n        float *getImpulseResponse() { return m_impulseResponse; }\n\n    protected:\n        float *m_shiftRegister;\n        int m_shiftOffset;\n\n        float *m_impulseResponse;\n        int m_sampleCount;\n};\n\n#endif /* ATG_ENGINE_SIM_CONVOLUTION_FILTER_H */\n"
  },
  {
    "path": "include/crankshaft.h",
    "content": "#ifndef ATG_ENGINE_SIM_CRANKSHAFT_H\n#define ATG_ENGINE_SIM_CRANKSHAFT_H\n\n#include \"part.h\"\n\nclass Crankshaft : public Part {\n    public:\n        struct Parameters {\n            double mass;\n            double flywheelMass;\n            double momentOfInertia;\n            double crankThrow;\n            double pos_x = 0;\n            double pos_y = 0;\n            double tdc = 0;\n            double frictionTorque = 0;\n            int rodJournals;\n        };\n\n    public:\n        Crankshaft();\n        virtual ~Crankshaft();\n\n        void initialize(const Parameters &params);\n        virtual void destroy();\n        inline int getRodJournalCount() const { return m_rodJournalCount; }\n        void setRodJournalAngle(int i, double angle);\n        void getRodJournalPositionLocal(int i, double *x, double *y);\n        void getRodJournalPositionGlobal(int i, double *x, double *y);\n        double getRodJournalAngle(int i) { return m_rodJournalAngles[i]; }\n\n        void resetAngle();\n\n        double getAngle() const;\n        double getCycleAngle(double offset = 0.0);\n\n        inline double getTdc() const { return m_tdc; }\n        inline double getThrow() const { return m_throw; }\n        inline double getMass() const { return m_m; }\n        inline double getMomentOfInertia() const { return m_I; }\n        inline double getFlywheelMass() const { return m_flywheelMass; }\n        inline double getPosX() const { return m_p_x; }\n        inline double getPosY() const { return m_p_y; }\n        inline double getFrictionTorque() const { return m_frictionTorque; }\n\n    protected:\n        double *m_rodJournalAngles;\n        int m_rodJournalCount;\n\n        double m_tdc;\n        double m_throw;\n        double m_m;\n        double m_I;\n        double m_flywheelMass;\n        double m_p_x;\n        double m_p_y;\n        double m_frictionTorque;\n};\n\n#endif /* ATG_ENGINE_SIM_CRANKSHAFT_H */\n"
  },
  {
    "path": "include/crankshaft_object.h",
    "content": "#ifndef ATG_ENGINE_SIM_CRANKSHAFT_OBJECT_H\n#define ATG_ENGINE_SIM_CRANKSHAFT_OBJECT_H\n\n#include \"simulation_object.h\"\n\n#include \"crankshaft.h\"\n#include \"geometry_generator.h\"\n\nclass CrankshaftObject : public SimulationObject {\n    public:\n        CrankshaftObject();\n        virtual ~CrankshaftObject();\n\n        virtual void generateGeometry();\n        virtual void render(const ViewParameters *view);\n        virtual void process(float dt);\n        virtual void destroy();\n\n        Crankshaft *m_crankshaft;\n};\n\n#endif /* ATG_ENGINE_SIM_CRANKSHAFT_OBJECT_H */\n"
  },
  {
    "path": "include/csv_io.h",
    "content": "#ifndef ATG_ENGINE_SIM_CSV_IO_H\n#define ATG_ENGINE_SIM_CSV_IO_H\n\n#include \"../dependencies/submodules/csv-io/include/csv_data.h\"\n\n#endif /* ATG_ENGINE_SIM_CSV_IO_H */\n"
  },
  {
    "path": "include/cylinder_bank.h",
    "content": "#ifndef ATG_ENGINE_SIM_CYLINDER_BANK_H\n#define ATG_ENGINE_SIM_CYLINDER_BANK_H\n\n#include \"part.h\"\n\n#include \"crankshaft.h\"\n\nclass CylinderBank {\n    public:\n        struct Parameters {\n            Crankshaft *crankshaft;\n            double positionX;\n            double positionY;\n            double angle;\n            double bore;\n            double deckHeight;\n            double displayDepth;\n            int cylinderCount;\n            int index;\n        };\n\n    public:\n        CylinderBank();\n        ~CylinderBank();\n\n        void initialize(const Parameters &params);\n        void destroy();\n\n        void getPositionAboveDeck(double h, double *x, double *y) const;\n        double boreSurfaceArea() const;\n\n        inline double getAngle() const { return m_angle; }\n        inline double getBore() const { return m_bore; }\n        inline double getDeckHeight() const { return m_deckHeight; }\n        inline int getCylinderCount() const { return m_cylinderCount; }\n        inline int getIndex() const { return m_index; }\n        inline double getDx() const { return m_dx; }\n        inline double getDy() const { return m_dy; }\n        inline double getX() const { return m_x; }\n        inline double getY() const { return m_y; }\n        inline double getDisplayDepth() const { return m_displayDepth; }\n\n    protected:\n        double m_angle;\n        double m_bore;\n        double m_deckHeight;\n        double m_displayDepth;\n        int m_cylinderCount;\n        int m_index;\n\n        double m_dx;\n        double m_dy;\n        double m_x;\n        double m_y;\n};\n\n#endif /* ATG_ENGINE_SIM_CYLINDER_BANK_H */\n"
  },
  {
    "path": "include/cylinder_bank_object.h",
    "content": "#ifndef ATG_ENGINE_SIM_CYLINDER_BANK_OBJECT_H\n#define ATG_ENGINE_SIM_CYLINDER_BANK_OBJECT_H\n\n#include \"simulation_object.h\"\n\n#include \"geometry_generator.h\"\n#include \"cylinder_bank.h\"\n#include \"cylinder_head.h\"\n\nclass CylinderBankObject : public SimulationObject {\n    public:\n        CylinderBankObject();\n        virtual ~CylinderBankObject();\n\n        virtual void generateGeometry();\n        virtual void render(const ViewParameters *view);\n        virtual void process(float dt);\n        virtual void destroy();\n\n        CylinderBank *m_bank;\n        CylinderHead *m_head;\n        GeometryGenerator::GeometryIndices m_walls;\n};\n\n#endif /* ATG_ENGINE_SIM_CYLINDER_BANK_OBJECT_H */\n"
  },
  {
    "path": "include/cylinder_head.h",
    "content": "#ifndef ATG_ENGINE_SIM_CYLINDER_HEAD_H\n#define ATG_ENGINE_SIM_CYLINDER_HEAD_H\n\n#include \"part.h\"\n\n#include \"function.h\"\n#include \"camshaft.h\"\n#include \"exhaust_system.h\"\n#include \"intake.h\"\n\nclass Valvetrain;\nclass CylinderBank;\nclass CylinderHead : public Part {\n    public:\n        struct Parameters {\n            CylinderBank *Bank;\n\n            Function *ExhaustPortFlow;\n            Function *IntakePortFlow;\n\n            Valvetrain *Valvetrain;\n\n            double CombustionChamberVolume;\n\n            double IntakeRunnerVolume;\n            double IntakeRunnerCrossSectionArea;\n            double ExhaustRunnerVolume;\n            double ExhaustRunnerCrossSectionArea;\n\n            bool FlipDisplay = false;\n        };\n\n        struct Cylinder {\n            ExhaustSystem *exhaustSystem = nullptr;\n            Intake *intake = nullptr;\n\n            double soundAttenuation = 1.0;\n            double headerPrimaryLength = 0.0;\n        };\n\n    public:\n        CylinderHead();\n        virtual ~CylinderHead();\n\n        void initialize(const Parameters &params);\n        virtual void destroy();\n\n        double intakeFlowRate(int cylinder) const;\n        double exhaustFlowRate(int cylinder) const;\n        double intakeValveLift(int cylinder) const;\n        double exhaustValveLift(int cylinder) const;\n\n        inline ExhaustSystem *getExhaustSystem(int cylinderIndex) const { return m_cylinders[cylinderIndex].exhaustSystem; }\n        void setAllExhaustSystems(ExhaustSystem *system);\n        void setExhaustSystem(int i, ExhaustSystem *system);\n\n        inline double getSoundAttenuation(int cylinderIndex) const { return m_cylinders[cylinderIndex].soundAttenuation; }\n        void setSoundAttenuation(int i, double soundAttenuation);\n\n        inline Intake *getIntake(int cylinderIndex) const { return m_cylinders[cylinderIndex].intake; }\n        void setAllIntakes(Intake *intake);\n        void setIntake(int i, Intake *intake);\n\n        inline double getHeaderPrimaryLength(int cylinderIndex) const { return m_cylinders[cylinderIndex].headerPrimaryLength; }\n        void setAllHeaderPrimaryLengths(double length);\n        void setHeaderPrimaryLength(int i, double length);\n\n        inline bool getFlipDisplay() const { return m_flipDisplay; }\n        inline double getCombustionChamberVolume() const { return m_combustionChamberVolume; }\n        inline CylinderBank *getCylinderBank() const { return m_bank; }\n\n        double getIntakeRunnerVolume() const { return m_intakeRunnerVolume; }\n        double getIntakeRunnerCrossSectionArea() const { return m_intakeRunnerCrossSectionArea; }\n        double getExhaustRunnerVolume() const { return m_exhaustRunnerVolume; }\n        double getExhaustRunnerCrossSectionArea() const { return m_exhaustRunnerCrossSectionArea; }\n\n        Camshaft *getExhaustCamshaft();\n        Camshaft *getIntakeCamshaft();\n\n    protected:\n        Cylinder *m_cylinders;\n\n        CylinderBank *m_bank;\n        Valvetrain *m_valvetrain;\n\n        Function *m_exhaustPortFlow;\n        Function *m_intakePortFlow;\n\n        double m_intakeRunnerVolume;\n        double m_intakeRunnerCrossSectionArea;\n        double m_exhaustRunnerVolume;\n        double m_exhaustRunnerCrossSectionArea;\n\n        double m_combustionChamberVolume;\n        bool m_flipDisplay;\n};\n\n#endif /* ATG_ENGINE_SIM_CYLINDER_HEAD_H */\n"
  },
  {
    "path": "include/cylinder_head_object.h",
    "content": "#ifndef ATG_ENGINE_SIM_CYLINDER_HEAD_OBJECT_H\n#define ATG_ENGINE_SIM_CYLINDER_HEAD_OBJECT_H\n\n#include \"simulation_object.h\"\n\n#include \"cylinder_head.h\"\n#include \"geometry_generator.h\"\n#include \"engine.h\"\n\nclass CylinderHeadObject : public SimulationObject {\n    public:\n        CylinderHeadObject();\n        virtual ~CylinderHeadObject();\n\n        virtual void generateGeometry();\n        virtual void render(const ViewParameters *view);\n        virtual void process(float dt);\n        virtual void destroy();\n\n        CylinderHead *m_head;\n        Engine *m_engine;\n\n    protected:\n        void generateCamshaft(\n            Camshaft *camshaft,\n            double padding,\n            double rollerRadius,\n            GeometryGenerator::GeometryIndices *indices);\n};\n\n#endif /* ATG_ENGINE_SIM_CYLINDER_HEAD_OBJECT_H */\n"
  },
  {
    "path": "include/cylinder_pressure_gauge.h",
    "content": "#ifndef ATG_ENGINE_SIM_UI_CYLINDER_PRESSURE_GAUGE_H\n#define ATG_ENGINE_SIM_UI_CYLINDER_PRESSURE_GAUGE_H\n\n#include \"ui_element.h\"\n\n#include \"engine.h\"\n#include \"gauge.h\"\n\nclass CylinderPressureGauge : public UiElement {\n    public:\n        CylinderPressureGauge();\n        virtual ~CylinderPressureGauge();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n\n        Engine *m_engine;\n\n    protected:\n        std::vector<Gauge *> m_gauges;\n};\n\n#endif /* ATG_ENGINE_SIM_UI_CYLINDER_PRESSURE_GAUGE_H */\n"
  },
  {
    "path": "include/cylinder_temperature_gauge.h",
    "content": "#ifndef ATG_ENGINE_SIM_CYLINDER_TEMPERATURE_GAUGE_H\n#define ATG_ENGINE_SIM_CYLINDER_TEMPERATURE_GAUGE_H\n\n#include \"ui_element.h\"\n\n#include \"engine.h\"\n#include \"gauge.h\"\n\nclass CylinderTemperatureGauge : public UiElement {\n    public:\n        CylinderTemperatureGauge();\n        virtual ~CylinderTemperatureGauge();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n\n        Engine *m_engine;\n        double m_maxTemperature;\n        double m_minTemperature;\n};\n\n#endif /* ATG_ENGINE_SIM_CYLINDER_TEMPERATURE_GAUGE_H */\n"
  },
  {
    "path": "include/delay_filter.h",
    "content": "#ifndef ATG_ENGINE_SIM_DELAY_FILTER_H\n#define ATG_ENGINE_SIM_DELAY_FILTER_H\n\n#include \"filter.h\"\n\n#include \"ring_buffer.h\"\n\n#include <cmath>\n\nclass DelayFilter : public Filter {\npublic:\n    DelayFilter() {\n        m_latencySamples = 0;\n    }\n\n    virtual ~DelayFilter() {\n        /* void */\n    }\n\n    void initialize(double delay, double audioFrequency) {\n        const int samples = static_cast<int>(std::round(delay * audioFrequency));\n        const int capacity = samples + 32;\n\n        m_history.initialize(capacity);\n        m_latencySamples = samples;\n    }\n\n    virtual float f(float sample) override {\n        return static_cast<float>(fast_f(static_cast<double>(sample)));\n    }\n\n    inline double fast_f(double sample) {\n        m_history.write(sample);\n\n        if (m_history.size() <= static_cast<size_t>(m_latencySamples)) {\n            return 0;\n        }\n        else {\n            double v;\n            m_history.readAndRemove(1, &v);\n\n            return v;\n        }\n    }\n\nprotected:\n    int m_latencySamples;\n    RingBuffer<double> m_history;\n};\n\n#endif /* ATG_ENGINE_SIM_DELAY_FILTER_H */\n"
  },
  {
    "path": "include/delta.h",
    "content": "#ifndef DELTA_TEMPLATE_DELTA_INCLUDES_H\n#define DELTA_TEMPLATE_DELTA_INCLUDES_H\n\n#include <delta-studio/include/yds_core.h>\n#include <delta-studio/engines/basic/include/delta_basic_engine.h>\n\n#endif /* DELTA_TEMPLATE_DELTA_INCLUDES_H */\n"
  },
  {
    "path": "include/derivative_filter.h",
    "content": "#ifndef ATG_ENGINE_SIM_DERIVATIVE_FILTER_H\n#define ATG_ENGINE_SIM_DERIVATIVE_FILTER_H\n\n#include \"filter.h\"\n\nclass DerivativeFilter : public Filter {\n    public:\n        DerivativeFilter();\n        virtual ~DerivativeFilter();\n\n        virtual float f(float sample) override;\n\n        float m_dt;\n\n    protected:\n        float m_previous;\n};\n\n#endif /* ATG_ENGINE_SIM_DERIVATIVE_FILTER_H */\n"
  },
  {
    "path": "include/direct_throttle_linkage.h",
    "content": "#ifndef ATG_ENGINE_SIM_DIRECT_THROTTLE_LINKAGE_H\n#define ATG_ENGINE_SIM_DIRECT_THROTTLE_LINKAGE_H\n\n#include \"throttle.h\"\n\nclass DirectThrottleLinkage : public Throttle {\npublic:\n    struct Parameters {\n        double gamma;\n    };\n\npublic:\n    DirectThrottleLinkage();\n    virtual ~DirectThrottleLinkage();\n\n    void initialize(const Parameters &params);\n\n    virtual void setSpeedControl(double s);\n    virtual void update(double dt, Engine *engine);\n\nprotected:\n    double m_gamma;\n    double m_throttlePosition;\n};\n\n#endif /* ATG_ENGINE_SIM_DIRECT_THROTTLE_LINKAGE_H */\n"
  },
  {
    "path": "include/dtv.h",
    "content": "#ifndef ATG_ENGINE_SIM_DTV_H\n#define ATG_ENGINE_SIM_DTV_H\n\n#include \"../dependencies/submodules/direct-to-video/include/dtv.h\"\n\n#endif /* ATG_ENGINE_SIM_DTV_H */\n"
  },
  {
    "path": "include/dynamometer.h",
    "content": "#ifndef ATG_ENGINE_SIM_DYNAMOMETER_H\n#define ATG_ENGINE_SIM_DYNAMOMETER_H\n\n#include \"scs.h\"\n\n#include \"crankshaft.h\"\n\nclass Dynamometer : public atg_scs::Constraint {\n    public:\n        Dynamometer();\n        virtual ~Dynamometer();\n\n        void connectCrankshaft(Crankshaft *crankshaft);\n        virtual void calculate(Output *output, atg_scs::SystemState *state);\n        double getTorque() const;\n\n        double m_rotationSpeed;\n        double m_ks;\n        double m_kd;\n        double m_maxTorque;\n\n        bool m_hold;\n        bool m_enabled;\n};\n\n#endif /* ATG_ENGINE_SIM_DYNAMOMETER_H */\n"
  },
  {
    "path": "include/engine.h",
    "content": "#ifndef ATG_ENGINE_SIM_ENGINE_H\n#define ATG_ENGINE_SIM_ENGINE_H\n\n#include \"part.h\"\n\n#include \"piston.h\"\n#include \"connecting_rod.h\"\n#include \"crankshaft.h\"\n#include \"cylinder_bank.h\"\n#include \"cylinder_head.h\"\n#include \"exhaust_system.h\"\n#include \"ignition_module.h\"\n#include \"intake.h\"\n#include \"combustion_chamber.h\"\n#include \"units.h\"\n#include \"throttle.h\"\n\n#include <string>\n\nclass Simulator;\nclass Vehicle;\nclass Transmission;\nclass Engine : public Part {\n    public:\n        struct Parameters {\n            int cylinderBanks;\n            int cylinderCount;\n            int crankshaftCount;\n            int exhaustSystemCount;\n            int intakeCount;\n\n            std::string name;\n\n            double starterTorque = units::torque(90.0, units::ft_lb);\n            double starterSpeed = units::rpm(200);\n            double redline = units::rpm(6500);\n            double dynoMinSpeed = units::rpm(1000);\n            double dynoMaxSpeed = units::rpm(6500);\n            double dynoHoldStep = units::rpm(100);\n\n            Throttle *throttle;\n\n            double initialSimulationFrequency;\n            double initialHighFrequencyGain;\n            double initialNoise;\n            double initialJitter;\n        };\n\n    public:\n        Engine();\n        virtual ~Engine();\n\n        void initialize(const Parameters &params);\n        virtual void destroy();\n\n        std::string getName() const { return m_name; }\n\n        virtual Crankshaft *getOutputCrankshaft() const;\n        virtual void setSpeedControl(double s);\n        virtual double getSpeedControl();\n        virtual void setThrottle(double throttle);\n        virtual double getThrottle() const;\n        virtual double getThrottlePlateAngle() const;\n        virtual void calculateDisplacement();\n        double getDisplacement() const { return m_displacement; }\n        virtual double getIntakeFlowRate() const;\n        virtual void update(double dt);\n\n        virtual double getManifoldPressure() const;\n        virtual double getIntakeAfr() const;\n        virtual double getExhaustO2() const;\n        virtual double getRpm() const;\n        virtual double getSpeed() const;\n        virtual bool isSpinningCw() const;\n\n        virtual void resetFuelConsumption();\n        virtual double getTotalFuelMassConsumed() const;\n        double getTotalVolumeFuelConsumed() const;\n\n        inline double getStarterTorque() const { return m_starterTorque; }\n        inline double getStarterSpeed() const { return m_starterSpeed; }\n        inline double getRedline() const { return m_redline; }\n        inline double getDynoMinSpeed() const { return m_dynoMinSpeed; }\n        inline double getDynoMaxSpeed() const { return m_dynoMaxSpeed; }\n        inline double getDynoHoldStep() const { return m_dynoHoldStep; }\n\n        int getCylinderBankCount() const { return m_cylinderBankCount; }\n        int getCylinderCount() const { return m_cylinderCount; }\n        int getCrankshaftCount() const { return m_crankshaftCount; }\n        int getExhaustSystemCount() const { return m_exhaustSystemCount; }\n        int getIntakeCount() const { return m_intakeCount; }\n        int getMaxDepth() const;\n\n        Crankshaft *getCrankshaft(int i) const { return &m_crankshafts[i]; }\n        CylinderBank *getCylinderBank(int i) const { return &m_cylinderBanks[i]; }\n        CylinderHead *getHead(int i) const { return &m_heads[i]; }\n        Piston *getPiston(int i) const { return &m_pistons[i]; }\n        ConnectingRod *getConnectingRod(int i) const { return &m_connectingRods[i]; }\n        IgnitionModule *getIgnitionModule() { return &m_ignitionModule; }\n        ExhaustSystem *getExhaustSystem(int i) const { return &m_exhaustSystems[i]; }\n        Intake *getIntake(int i) const { return &m_intakes[i]; }\n        CombustionChamber *getChamber(int i) const { return &m_combustionChambers[i]; }\n        Fuel *getFuel() { return &m_fuel; }\n\n        double getSimulationFrequency() const { return m_initialSimulationFrequency; }\n        double getInitialHighFrequencyGain() const { return m_initialHighFrequencyGain; }\n        double getInitialNoise() const { return m_initialNoise; }\n        double getInitialJitter() const { return m_initialJitter; }\n\n        virtual Simulator *createSimulator(Vehicle *vehicle, Transmission *transmission);\n\n    protected:\n        std::string m_name;\n\n        Crankshaft *m_crankshafts;\n        int m_crankshaftCount;\n\n        CylinderBank *m_cylinderBanks;\n        CylinderHead *m_heads;\n        int m_cylinderBankCount;\n\n        Piston *m_pistons;\n        ConnectingRod *m_connectingRods;\n        CombustionChamber *m_combustionChambers;\n        int m_cylinderCount;\n\n        double m_starterTorque;\n        double m_starterSpeed;\n        double m_redline;\n        double m_dynoMinSpeed;\n        double m_dynoMaxSpeed;\n        double m_dynoHoldStep;\n\n        double m_initialSimulationFrequency;\n        double m_initialHighFrequencyGain;\n        double m_initialNoise;\n        double m_initialJitter;\n\n        ExhaustSystem *m_exhaustSystems;\n        int m_exhaustSystemCount;\n\n        Intake *m_intakes;\n        int m_intakeCount;\n\n        IgnitionModule m_ignitionModule;\n        Fuel m_fuel;\n\n        Throttle *m_throttle;\n\n        double m_throttleValue;\n        double m_displacement;\n};\n\n#endif /* ATG_ENGINE_SIM_ENGINE_H */\n"
  },
  {
    "path": "include/engine_sim_application.h",
    "content": "#ifndef ATG_ENGINE_SIM_ENGINE_SIM_APPLICATION_H\n#define ATG_ENGINE_SIM_ENGINE_SIM_APPLICATION_H\n\n#include \"geometry_generator.h\"\n#include \"simulator.h\"\n#include \"engine.h\"\n#include \"simulation_object.h\"\n#include \"ui_manager.h\"\n#include \"dynamometer.h\"\n#include \"oscilloscope.h\"\n#include \"audio_buffer.h\"\n#include \"convolution_filter.h\"\n#include \"shaders.h\"\n#include \"engine_view.h\"\n#include \"right_gauge_cluster.h\"\n#include \"cylinder_temperature_gauge.h\"\n#include \"synthesizer.h\"\n#include \"oscilloscope_cluster.h\"\n#include \"performance_cluster.h\"\n#include \"load_simulation_cluster.h\"\n#include \"mixer_cluster.h\"\n#include \"info_cluster.h\"\n#include \"application_settings.h\"\n#include \"transmission.h\"\n\n#include \"delta.h\"\n#include \"dtv.h\"\n\n#include <vector>\n\nclass EngineSimApplication {\n    private:\n        static std::string s_buildVersion;\n\n    public:\n        EngineSimApplication();\n        virtual ~EngineSimApplication();\n\n        static std::string getBuildVersion() { return s_buildVersion; }\n\n        void initialize(void *instance, ysContextObject::DeviceAPI api);\n        void run();\n        void destroy();\n\n        void loadEngine(Engine *engine, Vehicle *vehicle, Transmission *transmission);\n        void drawGenerated(\n                const GeometryGenerator::GeometryIndices &indices,\n                int layer = 0);\n        void drawGeneratedUi(\n                const GeometryGenerator::GeometryIndices &indices,\n                int layer = 0);\n        void drawGenerated(\n                const GeometryGenerator::GeometryIndices &indices,\n                int layer,\n                dbasic::StageEnableFlags flags);\n        void configure(const ApplicationSettings &settings);\n        GeometryGenerator *getGeometryGenerator() { return &m_geometryGenerator; }\n\n        Shaders *getShaders() { return &m_shaders; }\n        dbasic::TextRenderer *getTextRenderer() { return &m_textRenderer; }\n\n        void createObjects(Engine *engine);\n        void destroyObjects();\n        dbasic::DeltaEngine *getEngine() { return &m_engine; }\n\n        float pixelsToUnits(float pixels) const;\n        float unitsToPixels(float units) const;\n\n        ysVector getBackgroundColor() const { return m_background; }\n        ysVector getForegroundColor() const { return m_foreground; }\n        ysVector getHightlight1Color() const { return m_highlight1; }\n        ysVector getPink() const { return m_pink; }\n        ysVector getGreen() const { return m_green; }\n        ysVector getYellow() const { return m_yellow; }\n        ysVector getRed() const { return m_red; }\n        ysVector getOrange() const { return m_orange; }\n        ysVector getBlue() const { return m_blue; }\n\n        const SimulationObject::ViewParameters &getViewParameters() const;\n        void setViewLayer(int view) { m_viewParameters.Layer0 = view; }\n\n        dbasic::AssetManager *getAssetManager() { return &m_assetManager; }\n\n        int getScreenWidth() const { return m_screenWidth; }\n        int getScreenHeight() const { return m_screenHeight; }\n\n        Simulator *getSimulator() { return m_simulator; }\n        InfoCluster *getInfoCluster() { return m_infoCluster; }\n        ApplicationSettings* getAppSettings() { return &m_applicationSettings; }\n\n    protected:\n        void loadScript();\n        void processEngineInput();\n        void renderScene();\n\n        void refreshUserInterface();\n\n    protected:\n        double m_speedSetting = 1.0;\n        double m_targetSpeedSetting = 1.0;\n\n        double m_clutchPressure = 1.0;\n        double m_targetClutchPressure = 1.0;\n        int m_lastMouseWheel = 0;\n\n    protected:\n        virtual void initialize();\n        virtual void process(float dt);\n        virtual void render();\n\n        float m_displayAngle;\n        float m_displayHeight;\n        int m_gameWindowHeight;\n        int m_screenWidth;\n        int m_screenHeight;\n        \n        ApplicationSettings m_applicationSettings;\n        dbasic::ShaderSet m_shaderSet;\n        Shaders m_shaders;\n\n        dbasic::DeltaEngine m_engine;\n        dbasic::AssetManager m_assetManager;\n\n        std::string m_assetPath;\n\n        ysRenderTarget *m_mainRenderTarget;\n        ysGPUBuffer *m_geometryVertexBuffer;\n        ysGPUBuffer *m_geometryIndexBuffer;\n\n        GeometryGenerator m_geometryGenerator;\n        dbasic::TextRenderer m_textRenderer;\n\n        std::vector<SimulationObject *> m_objects;\n        Engine *m_iceEngine;\n        Vehicle *m_vehicle;\n        Transmission *m_transmission;\n        Simulator *m_simulator;\n        double m_dynoSpeed;\n        double m_torque;\n\n        UiManager m_uiManager;\n        EngineView *m_engineView;\n        RightGaugeCluster *m_rightGaugeCluster;\n        OscilloscopeCluster *m_oscCluster;\n        CylinderTemperatureGauge *m_temperatureGauge;\n        PerformanceCluster *m_performanceCluster;\n        LoadSimulationCluster *m_loadSimulationCluster;\n        MixerCluster *m_mixerCluster;\n        InfoCluster *m_infoCluster;\n        SimulationObject::ViewParameters m_viewParameters;\n\n        bool m_paused;\n\n    protected:\n        void startRecording();\n        void updateScreenSizeStability();\n        bool readyToRecord();\n        void stopRecording();\n        void recordFrame();\n        bool isRecording() const { return m_recording; }\n\n        static constexpr int ScreenResolutionHistoryLength = 5;\n        int m_screenResolution[ScreenResolutionHistoryLength][2];\n        int m_screenResolutionIndex;\n        bool m_recording;\n\n        ysVector m_background;\n        ysVector m_foreground;\n        ysVector m_shadow;\n        ysVector m_highlight1;\n        ysVector m_highlight2;\n\n        ysVector m_pink;\n        ysVector m_orange;\n        ysVector m_yellow;\n        ysVector m_red;\n        ysVector m_green;\n        ysVector m_blue;\n\n        ysAudioBuffer *m_outputAudioBuffer;\n        AudioBuffer m_audioBuffer;\n        ysAudioSource *m_audioSource;\n\n        int m_oscillatorSampleOffset;\n        int m_screen;\n\n#ifdef ATG_ENGINE_SIM_VIDEO_CAPTURE\n        atg_dtv::Encoder m_encoder;\n#endif /* ATG_ENGINE_SIM_VIDEO_CAPTURE */\n};\n\n#endif /* ATG_ENGINE_SIM_ENGINE_SIM_APPLICATION_H */\n"
  },
  {
    "path": "include/engine_view.h",
    "content": "#ifndef ATG_ENGINE_SIM_ENGINE_VIEW_H\n#define ATG_ENGINE_SIM_ENGINE_VIEW_H\n\n#include \"ui_element.h\"\n\nclass EngineView : public UiElement {\n    public:\n        EngineView();\n        virtual ~EngineView();\n\n        virtual void update(float dt);\n        virtual void render();\n        virtual void onMouseDown(const Point &mouseLocal);\n        virtual void onDrag(const Point &p0, const Point &mouse0, const Point &mouse);\n        virtual void onMouseScroll(int scroll);\n\n        void setDrawFrame(bool drawFrame) { m_drawFrame = drawFrame; }\n        void setBounds(const Bounds &bounds);\n\n        Point getCenter() const;\n\n        Point getCameraPosition() const;\n        float m_zoom;\n        \n    protected:\n        Point m_pan;\n        Point m_dragStart;\n        int m_lastScroll;\n        bool m_drawFrame;\n};\n\n#endif /* ATG_ENGINE_SIM_ENGINE_VIEW_H */\n"
  },
  {
    "path": "include/exhaust_system.h",
    "content": "#ifndef ATG_ENGINE_SIM_EXHAUST_SYSTEM_H\n#define ATG_ENGINE_SIM_EXHAUST_SYSTEM_H\n\n#include \"part.h\"\n\n#include \"gas_system.h\"\n#include \"impulse_response.h\"\n\nclass ExhaustSystem : public Part {\n    friend class Engine;\n\n    public:\n        struct Parameters {\n            double length;\n            double collectorCrossSectionArea;\n            double outletFlowRate;\n            double primaryTubeLength;\n            double primaryFlowRate;\n            double velocityDecay;\n            double audioVolume;\n            ImpulseResponse *impulseResponse;\n        };\n\n    public:\n        ExhaustSystem();\n        virtual ~ExhaustSystem();\n\n        void initialize(const Parameters &params);\n        virtual void destroy();\n\n        void process(double dt);\n\n        inline int getIndex() const { return m_index; }\n        inline double getLength() const { return m_length; }\n        inline double getFlow() const { return m_flow; }\n        inline double getAudioVolume() const { return m_audioVolume; }\n        inline double getPrimaryFlowRate() const { return m_primaryFlowRate; }\n        inline double getCollectorCrossSectionArea() const { return m_collectorCrossSectionArea; }\n        inline double getPrimaryTubeLength() const { return m_primaryTubeLength; }\n        inline double getVelocityDecay() const { return m_velocityDecay; }\n        inline ImpulseResponse *getImpulseResponse() const { return m_impulseResponse; }\n\n        inline GasSystem *getSystem() { return &m_system; }\n\n    protected:\n        GasSystem m_atmosphere;\n        GasSystem m_system;\n\n        ImpulseResponse *m_impulseResponse;\n\n        double m_length;\n        double m_primaryTubeLength;\n        double m_collectorCrossSectionArea;\n        double m_primaryFlowRate;\n        double m_outletFlowRate;\n        double m_audioVolume;\n        double m_velocityDecay;\n        int m_index;\n\n        double m_flow;\n};\n\n#endif /* ATG_ENGINE_SIM_EXHAUST_SYSTEM_H */\n"
  },
  {
    "path": "include/feedback_comb_filter.h",
    "content": "#ifndef ATG_ENGINE_SIM_FEEDBACK_COMB_FILTER_H\n#define ATG_ENGINE_SIM_FEEDBACK_COMB_FILTER_H\n\n#include \"filter.h\"\n\nclass FeedbackCombFilter : public Filter {\n    public:\n        FeedbackCombFilter();\n        virtual ~FeedbackCombFilter();\n\n        void initialize(int M);\n        virtual float f(float sample) override;\n        virtual void destroy();\n\n        float a_M;\n\n    protected:\n        float *m_y;\n        int m_offset;\n\n    protected:\n        int M;\n};\n\n#endif /* ATG_ENGINE_SIM_FEEDBACK_COMB_FILTER_H */\n"
  },
  {
    "path": "include/filter.h",
    "content": "#ifndef ATG_ENGINE_SIM_FILTER_H\n#define ATG_ENGINE_SIM_FILTER_H\n\nclass Filter {\n    public:\n        Filter();\n        virtual ~Filter();\n\n        virtual float f(float sample);\n        virtual void destroy();\n};\n\n#endif /* ATG_ENGINE_SIM_FILTER_H */\n"
  },
  {
    "path": "include/firing_order_display.h",
    "content": "#ifndef ATG_ENGINE_SIM_FIRING_ORDER_DISPLAY_H\n#define ATG_ENGINE_SIM_FIRING_ORDER_DISPLAY_H\n\n#include \"ui_element.h\"\n\n#include \"engine.h\"\n#include \"gauge.h\"\n\nclass FiringOrderDisplay : public UiElement {\n    public:\n        FiringOrderDisplay();\n        virtual ~FiringOrderDisplay();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n\n        Engine *m_engine;\n\n    protected:\n        int m_cylinderCount;\n        float *m_cylinderLit;\n};\n\n#endif /* ATG_ENGINE_SIM_FIRING_ORDER_DISPLAY_H */\n"
  },
  {
    "path": "include/fuel.h",
    "content": "#ifndef ATG_ENGINE_FUEL_H\n#define ATG_ENGINE_FUEL_H\n\n#include \"units.h\"\n\n#include \"function.h\"\n\n#include <string>\n\nclass Fuel {\n    public:\n        struct Parameters {\n            std::string name = \"Gasoline\";\n            double molecularMass =\n                units::mass(100.0, units::g);\n            double energyDensity =\n                units::energy(48.1, units::kJ) / units::mass(1.0, units::g);\n            double density =\n                units::mass(0.755, units::kg) / units::volume(1.0, units::L);\n            double molecularAfr = 25 / 2.0;\n            double burningEfficiencyRandomness = 0.5;\n            double lowEfficiencyAttenuation = 0.6;\n            double maxBurningEfficiency = 0.8;\n            double maxTurbulenceEffect = 2.0;\n            double maxDilutionEffect = 50.0;\n            Function *turbulenceToFlameSpeedRatio = nullptr;\n        };\n\n        Fuel();\n        ~Fuel();\n\n        void initialize(const Parameters &params);\n\n        inline double getMolecularMass() const { return m_molecularMass; }\n        inline double getEnergyDensity() const { return m_energyDensity; }\n        inline double getDensity() const { return m_density; }\n        inline double getBurningEfficiencyRandomness() const { return m_burningEfficiencyRandomness; }\n        inline double getLowEfficiencyAttenuation() const { return m_lowEfficiencyAttenuation;  }\n        inline double getMaxBurningEfficiency() const { return m_maxBurningEfficiency; }\n        inline double getMaxTurbulenceEffect() const { return m_maxTurbulenceEffect; }\n        inline double getMaxDilutionEffect() const { return m_maxDilutionEffect; }\n\n        double flameSpeed(\n            double turbulence,\n            double molecularAfr,\n            double T,\n            double P,\n            double firingPressure,\n            double motoringPressure) const;\n        virtual double laminarBurningVelocity(double molecularAfr, double T, double P) const;\n\n        double getMolecularAfr() const { return m_molecularAfr; }\n\n    protected:\n        std::string m_name;\n        double m_molecularMass;\n        double m_energyDensity;\n        double m_density;\n        double m_molecularAfr;\n        double m_maxBurningEfficiency;\n        double m_burningEfficiencyRandomness;\n        double m_lowEfficiencyAttenuation;\n        double m_maxTurbulenceEffect;\n        double m_maxDilutionEffect;\n\n        Function *m_turbulenceToFlameSpeedRatio;\n};\n\n#endif /* ATG_ENGINE_FUEL_H */\n"
  },
  {
    "path": "include/fuel_cluster.h",
    "content": "#ifndef ATG_ENGINE_SIM_FUEL_CLUSTER_H\n#define ATG_ENGINE_SIM_FUEL_CLUSTER_H\n\n#include \"ui_element.h\"\n\n#include \"engine.h\"\n#include \"simulator.h\"\n\nclass FuelCluster : public UiElement {\n    public:\n        FuelCluster();\n        virtual ~FuelCluster();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n\n        Engine *m_engine;\n        Simulator *m_simulator;\n\n    private:\n        double getTotalVolumeFuelConsumed() const;\n};\n\n#endif /* ATG_ENGINE_SIM_FUEL_CLUSTER_H */\n"
  },
  {
    "path": "include/function.h",
    "content": "#ifndef ATG_ENGINE_SIM_FUNCTION_H\n#define ATG_ENGINE_SIM_FUNCTION_H\n\n#include \"gaussian_filter.h\"\n\nclass Function {\n    protected:\n        static GaussianFilter *DefaultGaussianFilter;\n\n    public:\n        Function();\n        virtual ~Function();\n\n        void initialize(int size, double filterRadius, GaussianFilter *filter = nullptr);\n        void resize(int newCapacity);\n        void destroy();\n\n        void setInputScale(double s) { m_inputScale = s; }\n        void setOutputScale(double s) { m_outputScale = s; }\n        void addSample(double x, double y);\n\n        double sampleTriangle(double x) const;\n        double sampleGaussian(double x) const;\n        double triangle(double x) const;\n        int closestSample(double x) const;\n\n        bool isOrdered() const;\n\n        void getDomain(double *x0, double *x1);\n        void getRange(double *y0, double *y1);\n\n    protected:\n        double *m_x;\n        double *m_y;\n\n        double m_yMin;\n        double m_yMax;\n        double m_inputScale;\n        double m_outputScale;\n\n        double m_filterRadius;\n\n        int m_capacity;\n        int m_size;\n\n        GaussianFilter *m_gaussianFilter;\n};\n\n#endif /* ATG_ENGINE_SIM_FUNCTION_H */\n"
  },
  {
    "path": "include/gas_system.h",
    "content": "#ifndef ATG_ENGINE_SIM_GAS_SYSTEM_H\n#define ATG_ENGINE_SIM_GAS_SYSTEM_H\n\n#include \"constants.h\"\n#include \"units.h\"\n\n#include <cfloat>\n#include <cmath>\n\nclass GasSystem {\n    public:\n        struct Mix {\n            double p_fuel = 0.0;\n            double p_inert = 1.0;\n            double p_o2 = 0.0;\n        };\n\n        struct State {\n            double n_mol = 0.0;\n            double E_k = 0.0;\n            double V = 0.0;\n            double momentum[2] = { 0.0, 0.0 };\n\n            Mix mix;\n        };\n\n        struct FlowParameters {\n            double k_flow;\n            double dt;\n            double direction_x, direction_y;\n            double crossSectionArea_0, crossSectionArea_1;\n            GasSystem *system_0, *system_1;\n        };\n\n    public:\n        GasSystem() { /* void */ }\n        ~GasSystem() { /* void */ }\n\n        void setGeometry(double width, double height, double dx, double dy);\n        void initialize(double P, double V, double T, const Mix &mix = {}, int degreesOfFreedom = 5);\n        void reset(double P, double T, const Mix &mix = {});\n\n        void setVolume(double V);\n        void setN(double n);\n\n        void changeVolume(double dV);\n        void changePressure(double pressure);\n        void changeTemperature(double dT);\n        void changeTemperature(double dT, double n);\n        void changeEnergy(double dE);\n        void changeMix(const Mix &mix);\n        void injectFuel(double n);\n\n        double react(double n, const Mix &mix);\n        static double flowConstant(double flowRate, double P, double pressureDrop, double T, double hcr);\n        static double k_28inH2O(double flowRateScfm);\n        static double k_carb(double flowRateScfm);\n        static double flowRate(\n            double k_flow,\n            double P0,\n            double P1,\n            double T0,\n            double T1,\n            double hcr,\n            double chokedFlowLimit,\n            double chokedFlowRateCached);\n        double loseN(double dn, double E_k_per_mol);\n        double gainN(double dn, double E_k_per_mol, const Mix &mix = {});\n        void dissipateExcessVelocity();\n\n        void updateVelocity(double dt, double beta = 1.0);\n        void dissipateVelocity(double dt, double timeConstant);\n\n        static double flow(const FlowParameters &params);\n        double flow(double k_flow, double dt, double P_env, double T_env, const Mix &mix = {});\n\n        double pressureEquilibriumMaxFlow(const GasSystem *b) const;\n        double pressureEquilibriumMaxFlow(double P_env, double T_env) const;\n\n        inline static constexpr double kineticEnergyPerMol(double T, int degreesOfFreedom);\n        inline static constexpr double heatCapacityRatio(int degreesOfFreedom);\n        inline static double chokedFlowLimit(int degreesOfFreedom);\n        inline static double chokedFlowRate(int degreesOfFreedom);\n\n        inline double approximateDensity() const;\n        inline int degreesOfFreedom() const { return m_degreesOfFreedom; }\n        inline double n() const;\n        inline double n(double V) const;\n        inline double kineticEnergy() const;\n        inline double kineticEnergy(double n) const;\n        inline double kineticEnergyPerMol() const { return kineticEnergy(1.0); }\n        inline double totalEnergy() const;\n        inline double bulkKineticEnergy() const;\n        inline double c() const;\n        inline double dynamicPressure(double dx, double dy) const;\n        inline double mass() const;\n        inline double pressure() const;\n        inline double temperature() const;\n        inline double velocity_x() const;\n        inline double velocity_y() const;\n        inline double volume() const;\n        inline double volume(double n) const;\n        inline double n_fuel() const;\n        inline double n_inert() const;\n        inline double n_o2() const;\n        inline double heatCapacityRatio() const;\n        inline Mix mix() const { return m_state.mix; }\n\n    protected:\n        State m_state;\n\n        int m_degreesOfFreedom = 5;\n\n        double m_chokedFlowLimit = 0;\n        double m_chokedFlowFactorCached = 0;\n\n        double m_width = 0.0;\n        double m_height = 0.0;\n        double m_dx = 0.0;\n        double m_dy = 0.0;\n};\n\ninline constexpr double GasSystem::kineticEnergyPerMol(double T, int degreesOfFreedom) {\n    return 0.5 * T * constants::R * degreesOfFreedom;\n}\n\ninline constexpr double GasSystem::heatCapacityRatio(int degreesOfFreedom) {\n    return 1.0 + (2.0 / degreesOfFreedom);\n}\n\ninline double GasSystem::chokedFlowLimit(int degreesOfFreedom) {\n    const double hcr = heatCapacityRatio(degreesOfFreedom);\n    return std::pow((2.0 / (hcr + 1)), hcr / (hcr - 1));\n}\n\ninline double GasSystem::chokedFlowRate(int degreesOfFreedom) {\n    const double hcr = heatCapacityRatio(degreesOfFreedom);\n    double flowRate =\n        std::sqrt(hcr) * std::pow(2 / (hcr + 1), (hcr + 1) / (2 * (hcr - 1)));\n\n    return flowRate;\n}\n\ninline double GasSystem::approximateDensity() const {\n    return (units::AirMolecularMass * n()) / volume();\n}\n\ninline double GasSystem::n() const {\n    return m_state.n_mol;\n}\n\ninline double GasSystem::n(double V) const {\n    return (V / volume()) * n();\n}\n\ninline double GasSystem::kineticEnergy() const {\n    return m_state.E_k;\n}\n\ninline double GasSystem::kineticEnergy(double n) const {\n    return (kineticEnergy() / this->n()) * n;\n}\n\ninline double GasSystem::c() const {\n    if (n() == 0 || kineticEnergy() == 0) return 0;\n\n    const double hcr = heatCapacityRatio();\n    const double staticPressure = pressure();\n    const double density = approximateDensity();\n    const double c = std::sqrt(staticPressure * hcr / density);\n\n    return c;\n}\n\ninline double GasSystem::totalEnergy() const {\n    if (n() == 0) return 0;\n\n    const double invMass = 1 / mass();\n    const double v_x = m_state.momentum[0] * invMass;\n    const double v_y = m_state.momentum[1] * invMass;\n    const double v_squared = v_x * v_x + v_y * v_y;\n\n    return kineticEnergy() + 0.5 * mass() * v_squared;\n}\n\ninline double GasSystem::bulkKineticEnergy() const {\n    const double m = mass();\n    if (m == 0) return 0;\n\n    const double v_x = m_state.momentum[0] / m;\n    const double v_y = m_state.momentum[1] / m;\n    const double v_squared = v_x * v_x + v_y * v_y;\n    return 0.5 * m * v_squared;\n}\n\ninline double GasSystem::dynamicPressure(double dx, double dy) const {\n    if (n() == 0 || kineticEnergy() == 0) return 0;\n\n    const double inverseMass = 1 / this->mass();\n    const double v = inverseMass * (dx * m_state.momentum[0] + dy * m_state.momentum[1]);\n\n    if (v <= 0) {\n        return 0;\n    }\n\n    const double hcr = heatCapacityRatio();\n    const double staticPressure = pressure();\n    const double density = approximateDensity();\n    const double c_squared = staticPressure * hcr / density;\n    const double machNumber_squared = v * v / c_squared;\n\n    // Below is equivalent to:\n    // staticPressure * pow(1 + ((hcr - 1) / 2) * machNumber * machNumber, hcr / (hcr - 1)) - 1)\n\n    const double x = 1 + ((hcr - 1) / 2) * machNumber_squared;\n    double x_d;\n    switch (m_degreesOfFreedom) {\n    case 3:\n        x_d = x * x * x * x * x;\n        break;\n    case 5:\n    {\n        const double x_2 = x * x;\n        const double x_3 = x_2 * x;\n        x_d = x_3 * x_3 * x;\n        break;\n    }\n    default:\n        x_d = x;\n    }\n\n    return staticPressure * (std::sqrt(x_d) - 1);\n}\n\ninline double GasSystem::mass() const {\n    return units::AirMolecularMass * n();\n}\n\ninline double GasSystem::pressure() const {\n    const double volume = this->volume();\n    return (volume != 0)\n        ? kineticEnergy() / (0.5 * m_degreesOfFreedom * volume)\n        : 0;\n}\n\ninline double GasSystem::temperature() const {\n    if (n() == 0) return 0;\n    else return kineticEnergy() / (0.5 * m_degreesOfFreedom * n() * constants::R);\n}\n\ninline double GasSystem::velocity_x() const {\n    if (n() == 0) return 0;\n    else return m_state.momentum[0] / mass();\n}\n\ninline double GasSystem::velocity_y() const {\n    if (n() == 0) return 0;\n    else return m_state.momentum[1] / mass();\n}\n\ninline double GasSystem::volume() const {\n    return m_state.V;\n}\n\ninline double GasSystem::volume(double n) const {\n    return n * this->n() / volume();\n}\n\ninline double GasSystem::n_fuel() const {\n    return m_state.mix.p_fuel * n();\n}\n\ninline double GasSystem::n_inert() const {\n    return m_state.mix.p_inert * n();\n}\n\ninline double GasSystem::n_o2() const {\n    return m_state.mix.p_o2 * n();\n}\n\ninline double GasSystem::heatCapacityRatio() const {\n    return heatCapacityRatio(m_degreesOfFreedom);\n}\n\n#endif /* ATG_ENGINE_SIM_GAS_SYSTEM_H */\n"
  },
  {
    "path": "include/gauge.h",
    "content": "#ifndef ATG_ENGINE_SIM_GAUGE_H\n#define ATG_ENGINE_SIM_GAUGE_H\n\n#include \"ui_element.h\"\n\n#include <string>\n\nclass Gauge : public UiElement {\n    public:\n        struct Band {\n            ysVector color = ysMath::Constants::One;\n            float start = 0.0;\n            float end = 0.0;\n            float width = 0.0;\n            float radial_offset = 0.0f;\n            float shorten_start = 0.0f;\n            float shorten_end = 0.0f;\n        };\n\n    public:\n        Gauge();\n        virtual ~Gauge();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n\n        void setBandCount(int bands) { m_bands.resize(bands); }\n        void setBand(const Band &band, int index) { m_bands[index] = band; }\n\n        float m_value;\n        float m_thetaMin;\n        float m_thetaMax;\n\n        int m_min;\n        int m_max;\n        int m_maxMinorTick;\n        float m_gamma;\n\n        int m_minorStep;\n        int m_majorStep;\n\n        float m_minorTickWidth;\n        float m_majorTickWidth;\n\n        float m_minorTickLength;\n        float m_majorTickLength;\n\n        float m_outerRadius;\n\n        float m_needleInnerRadius;\n        float m_needleOuterRadius;\n        float m_needleWidth;\n\n        float m_needleMaxVelocity;\n        float m_needleKs;\n        float m_needleKd;\n\n        bool m_renderText;\n\n        Point m_center;\n\n    protected:\n        float m_needlePosition;\n        float m_needleVelocity;\n\n    protected:\n        std::vector<Band> m_bands;\n};\n\n#endif /* ATG_ENGINE_SIM_GAUGE_H */\n"
  },
  {
    "path": "include/gaussian_filter.h",
    "content": "#ifndef ATG_ENGINE_SIM_GUASSIAN_FILTER_H\n#define ATG_ENGINE_SIM_GUASSIAN_FILTER_H\n\n#include \"scs.h\"\n\n#include \"crankshaft.h\"\n\nclass GaussianFilter {\n    public:\n        GaussianFilter();\n        ~GaussianFilter();\n\n        void initialize(double alpha, double radius, int cacheSteps=1024);\n        double evaluate(double s) const;\n\n        double getRadius() const { return m_radius; }\n        double getAlpha() const { return m_alpha; }\n\n    protected:\n        double calculate(double s) const;\n        void generateCache();\n\n    protected:\n        double *m_cache;\n\n        int m_cacheSteps;\n        double m_radius;\n        double m_alpha;\n\n        double m_exp_s;\n        double m_inv_r;\n};\n\n#endif /* ATG_ENGINE_SIM_GAUSSIAN_FILTER_H */\n"
  },
  {
    "path": "include/geometry_generator.h",
    "content": "#ifndef ATG_ENGINE_SIM_GEOMETRY_GENERATOR_H\n#define ATG_ENGINE_SIM_GEOMETRY_GENERATOR_H\n\n#include \"delta.h\"\n\n#include \"../include/function.h\"\n\n#include \"../include/ui_math.h\"\n\nclass GeometryGenerator {\npublic:\n    struct GeometryIndices {\n        int BaseIndex = -1;\n        int BaseVertex = -1;\n        int FaceCount = -1;\n\n        dbasic::Vertex *VertexData = nullptr;\n    };\n\n    struct LineRingParameters {\n        ysVector normal;\n        ysVector center;\n        float radius;\n        float patternHeight;\n        float maxEdgeLength;\n        float startAngle = 0.0f;\n        float endAngle = ysMath::Constants::TWO_PI;\n        float taperTail = 0.0f;\n        float textureOffset = 0.0f;\n        float textureWidthHeightRatio = 1.0f;\n    };\n\n    struct LineParameters {\n        ysVector start;\n        ysVector end;\n        ysVector normal = ysMath::Constants::ZAxis;\n        float patternHeight;\n        float taperTail;\n        float textureOffset = 0.0f;\n        float textureWidthHeightRatio = 1.0f;\n    };\n\n    struct Line2dParameters {\n        float x0, y0;\n        float x1, y1;\n        float lineWidth;\n    };\n\n    struct Ring2dParameters {\n        float center_x = 0.0f, center_y = 0.0f;\n        float startAngle = 0.0f, endAngle = ysMath::Constants::TWO_PI;\n        float innerRadius = 0.5f, outerRadius = 1.0f;\n        float maxEdgeLength = 2.0f;\n\n        bool drawArrow = false;\n        bool arrowOnEnd = true;\n        float arrowLength = 0.0f;\n    };\n\n    struct Circle2dParameters {\n        float center_x = 0.0f, center_y = 0.0f;\n        float radius = 1.0f;\n        float maxEdgeLength = 0.1f;\n        float smallestAngle = ysMath::Constants::PI * 0.95f;\n    };\n\n    struct Cam2dParameters {\n        float center_x = 0.0f, center_y = 0.0f;\n        float baseRadius = 1.0f;\n        float rollerRadius = 1.0f;\n        Function *lift = nullptr;\n        float maxEdgeLength = 0.1f;\n        float smallestAngle = ysMath::Constants::PI * 0.95f;\n    };\n\n    struct Rhombus2dParameters {\n        float center_x = 0.0f, center_y = 0.0f;\n        float height = 0;\n        float shear = 0;\n        float width = 0;\n    };\n\n    struct Trapezoid2dParameters {\n        float center_x = 0, center_y = 0;\n        float height = 0;\n        float base = 0;\n        float top = 0;\n    };\n\n    struct FrameParameters {\n        float x, y;\n        float frameWidth, frameHeight;\n        float lineWidth;\n    };\n\n    struct GridParameters {\n        float x, y;\n        float width, height;\n        float div_x, div_y;\n        float lineWidth;\n    };\n\n    struct PathParameters {\n        Point *p0;\n        Point *p1;\n        int n0;\n        int n1;\n\n        int i = 0;\n        float width;\n\n        int v0 = -1;\n        int v1 = -1;\n        float pdir_x, pdir_y;\n        float perp_x, perp_y;\n    };\n\npublic:\n    GeometryGenerator();\n    ~GeometryGenerator();\n\n    void initialize(int vertexBufferSize, int indexBufferSize);\n    void destroy();\n\n    const dbasic::Vertex *getVertexData() const { return m_vertexData; }\n    const unsigned short *getIndexData() const { return m_indexData; }\n\n    int getCurrentVertexCount() const { return m_state.vertexPointer; }\n    int getCurrentIndexCount() const { return m_state.indexPointer; }\n\n    void reset();\n\n    bool generateFilledCircle(\n        const ysVector &normal,\n        const ysVector &center,\n        float radius,\n        float maxEdgeLength\n    );\n\n    bool generateFilledFanPolygon(\n        const ysVector &normal,\n        const ysVector &up,\n        const ysVector &center,\n        float radius,\n        float rotation,\n        int segmentCount\n    );\n\n    bool generateLineRing(\n        const LineRingParameters &params);\n\n    bool generateLineRingBalanced(\n        const LineRingParameters &params);\n\n    bool generateLine(\n        const LineParameters &params);\n\n    bool generateLine2d(\n        const Line2dParameters &params);\n\n    bool generateRing2d(\n        const Ring2dParameters &params);\n\n    bool generateFrame(\n        const FrameParameters &params);\n\n    bool generateGrid(\n        const GridParameters &params);\n\n    bool generateCircle2d(\n        const Circle2dParameters &params);\n\n    bool generateCam(\n        const Cam2dParameters &params);\n\n    bool generateRhombus(\n        const Rhombus2dParameters &params);\n\n    bool generateTrapezoid2d(\n        const Trapezoid2dParameters &params);\n\n    bool generateIsoscelesTriangle(\n        float x, float y, float width, float height);\n\n    bool startPath(PathParameters &params);\n    bool generatePathSegment(PathParameters &params, bool detached = false);\n\n    void startShape();\n    void endShape(GeometryIndices *indices);\n\nprotected:\n    void startSubshape();\n\n    dbasic::Vertex *writeVertex();\n    void writeFace(unsigned short i0, unsigned short i1, unsigned short i2);\n\n    bool checkCapacity(int vertexCount, int indexCount);\n\nprotected:\n    static ysVector findOrthogonal(const ysVector &v);\n\nprotected:\n    dbasic::Vertex *m_vertexData;\n    unsigned short *m_indexData;\n\n    int m_vertexBufferSize;\n    int m_indexBufferSize;\n\n    struct State {\n        int vertexPointer = 0;\n        int indexPointer = 0;\n        GeometryIndices currentShape;\n        int subshapeVertexPointer = 0;\n    } m_state;\n};\n\n#endif /* ATG_ENGINE_SIM_GEOMETRY_GENERATOR_H */\n"
  },
  {
    "path": "include/governor.h",
    "content": "#ifndef ATG_ENGINE_SIM_GOVERNOR_H\n#define ATG_ENGINE_SIM_GOVERNOR_H\n\n#include \"throttle.h\"\n\nclass Governor : public Throttle {\npublic:\n    struct Parameters {\n        double minSpeed;\n        double maxSpeed;\n        double minVelocity;\n        double maxVelocity;\n        double k_s;\n        double k_d;\n        double gamma;\n    };\n\npublic:\n    Governor();\n    virtual ~Governor();\n\n    void initialize(const Parameters &params);\n\n    virtual void setSpeedControl(double s);\n    virtual void update(double dt, Engine *engine);\n\nprotected:\n    double m_minSpeed;\n    double m_maxSpeed;\n    double m_minVelocity;\n    double m_maxVelocity;\n    double m_k_s;\n    double m_k_d;\n    double m_gamma;\n\n    double m_targetSpeed;\n\n    double m_currentThrottle;\n    double m_velocity;\n};\n\n#endif /* ATG_ENGINE_SIM_GOVERNOR_H */\n"
  },
  {
    "path": "include/ignition_module.h",
    "content": "#ifndef ATG_ENGINE_SIM_IGNITION_MODULE_H\n#define ATG_ENGINE_SIM_IGNITION_MODULE_H\n\n#include \"part.h\"\n\n#include \"crankshaft.h\"\n#include \"function.h\"\n#include \"units.h\"\n\nclass IgnitionModule : public Part {\n    public:\n        struct Parameters {\n            int cylinderCount;\n            Crankshaft *crankshaft;\n            Function *timingCurve;\n            double revLimit = units::rpm(6000.0);\n            double limiterDuration = 0.5 * units::sec;\n        };\n\n        struct SparkPlug {\n            double angle = 0;\n            bool ignitionEvent = false;\n            bool enabled = false;\n        };\n\n    public:\n        IgnitionModule();\n        virtual ~IgnitionModule();\n\n        virtual void destroy();\n\n        void initialize(const Parameters &params);\n        void setFiringOrder(int cylinderIndex, double angle);\n        void reset();\n        void update(double dt);\n\n        bool getIgnitionEvent(int index) const;\n        void resetIgnitionEvents();\n\n        double getTimingAdvance();\n\n        bool m_enabled;\n\n    protected:\n        SparkPlug *getPlug(int i);\n\n        Function *m_timingCurve;\n        SparkPlug *m_plugs;\n        Crankshaft *m_crankshaft;\n        int m_cylinderCount;\n\n        double m_lastCrankshaftAngle;\n        double m_revLimit;\n        double m_revLimitTimer;\n        double m_limiterDuration;\n};\n\n#endif /* ATG_ENGINE_SIM_IGNITION_MODULE_H */\n"
  },
  {
    "path": "include/impulse_response.h",
    "content": "#ifndef ATG_ENGINE_SIM_IMPULSE_RESPONSE_H\n#define ATG_ENGINE_SIM_IMPULSE_RESPONSE_H\n\n#include <string>\n\nclass ImpulseResponse {\npublic:\n    ImpulseResponse();\n    virtual ~ImpulseResponse();\n\n    void initialize(const std::string &filename, double volume);\n    std::string getFilename() const { return m_filename; }\n    double getVolume() const { return m_volume; }\n\nprotected:\n    std::string m_filename;\n    double m_volume;\n};\n\n#endif /* ATG_ENGINE_SIM_IMPULSE_RESPONSE_H */\n"
  },
  {
    "path": "include/info_cluster.h",
    "content": "#ifndef ATG_ENGINE_SIM_INFO_CLUSTER_H\n#define ATG_ENGINE_SIM_INFO_CLUSTER_H\n\n#include \"ui_element.h\"\n\n#include \"engine.h\"\n\n#include <string>\n\nclass InfoCluster : public UiElement {\npublic:\n    InfoCluster();\n    virtual ~InfoCluster();\n\n    virtual void initialize(EngineSimApplication *app);\n    virtual void destroy();\n\n    virtual void update(float dt);\n    virtual void render();\n\n    void setEngine(Engine *engine) { m_engine = engine; }\n    void setLogMessage(const std::string &logMessage) { m_logMessage = logMessage; }\n    std::string getLogMessage() const { return m_logMessage; }\n\nprotected:\n    Engine *m_engine;\n\n    std::string m_logMessage;\n};\n\n#endif /* ATG_ENGINE_SIM_INFO_CLUSTER_H */\n"
  },
  {
    "path": "include/intake.h",
    "content": "#ifndef ATG_ENGINE_SIM_INTAKE_H\n#define ATG_ENGINE_SIM_INTAKE_H\n\n#include \"part.h\"\n\n#include \"gas_system.h\"\n\nclass Intake : public Part {\n    public:\n        struct Parameters {\n            // Plenum volume\n            double volume;\n\n            // Plenum dimensions\n            double CrossSectionArea;\n\n            // Input flow constant\n            double InputFlowK;\n\n            // Idle-circuit flow constant\n            double IdleFlowK;\n\n            // Flow rate from plenum to runner\n            double RunnerFlowRate;\n\n            // Molecular air fuel ratio (defaults to ideal for octane)\n            double MolecularAfr = (25.0 / 2.0);\n\n            // Throttle plate position at idle\n            double IdleThrottlePlatePosition = 0.975;\n\n            // Runner volume\n            double RunnerLength = units::distance(4.0, units::inch);\n\n            // Velocity decay factor\n            double VelocityDecay = 0.5;\n        };\n\n    public:\n        Intake();\n        virtual ~Intake();\n\n        void initialize(Parameters &params);\n        virtual void destroy();\n\n        void process(double dt);\n\n        inline double getRunnerFlowRate() const { return m_runnerFlowRate; }\n        inline double getThrottlePlatePosition() const { return m_idleThrottlePlatePosition * m_throttle; }\n        inline double getRunnerLength() const { return m_runnerLength; }\n        inline double getPlenumCrossSectionArea() const { return m_crossSectionArea; }\n        inline double getVelocityDecay() const { return m_velocityDecay; }\n\n        GasSystem m_system;\n        double m_throttle;\n\n        double m_flow;\n        double m_flowRate;\n        double m_totalFuelInjected;\n\n    protected:\n        double m_crossSectionArea;\n        double m_inputFlowK;\n        double m_idleFlowK;\n        double m_runnerFlowRate;\n        double m_molecularAfr;\n        double m_idleThrottlePlatePosition;\n        double m_runnerLength;\n        double m_velocityDecay;\n\n        GasSystem m_atmosphere;\n};\n\n#endif /* ATG_ENGINE_SIM_INTAKE_H */\n"
  },
  {
    "path": "include/jitter_filter.h",
    "content": "#ifndef ATG_ENGINE_SIM_JITTER_FILTER_H\n#define ATG_ENGINE_SIM_JITTER_FILTER_H\n\n#include \"filter.h\"\n\n#include \"butterworth_low_pass_filter.h\"\n#include \"utilities.h\"\n\n#include <random>\n\nclass JitterFilter : public Filter {\npublic:\n    JitterFilter();\n    virtual ~JitterFilter();\n\n    void initialize(\n        int maxJitter,\n        float noiseCutoffFrequency,\n        float audioFrequency);\n    virtual float f(float sample) override;\n\n    __forceinline float fast_f(float sample, float jitterScale = 1.0f) {\n        m_history[m_offset] = sample;\n        ++m_offset;\n\n        if (m_offset >= m_maxJitter) {\n            m_offset = 0;\n        }\n\n        std::uniform_real_distribution<float> dist(\n            0.0f,\n            static_cast<float>(m_maxJitter - 1));\n        \n        const float s = m_noiseFilter.fast_f(dist(m_generator) * m_jitterScale * jitterScale);\n        const float s_i_0 = clamp(std::floor(s), 0.0f, static_cast<float>(m_maxJitter - 1));\n        const float s_i_1 = clamp(std::ceil(s), 0.0f, static_cast<float>(m_maxJitter - 1));\n\n        const float s_frac = (s - s_i_0);\n\n        const int i_0 = static_cast<int>(s_i_0) + m_offset;\n        const int i_1 = static_cast<int>(s_i_1) + m_offset;\n\n        const float v0 = m_history[i_0 >= m_maxJitter ? i_0 - m_maxJitter : i_0];\n        const float v1 = m_history[i_1 >= m_maxJitter ? i_1 - m_maxJitter : i_1];\n\n        return v1 * s_frac + v0 * (1 - s_frac);\n    }\n\n    inline void setJitterScale(float jitterScale) { m_jitterScale = jitterScale; }\n    inline float getJitterScale() const { return m_jitterScale; }\n\nprotected:\n    ButterworthLowPassFilter<float> m_noiseFilter;\n\n    float m_jitterScale;\n    int m_maxJitter;\n    int m_offset;\n    float *m_history;\n\n    std::default_random_engine m_generator;\n};\n\n#endif /* ATG_ENGINE_SIM_JITTER_FILTER_H */\n"
  },
  {
    "path": "include/labeled_gauge.h",
    "content": "#ifndef ATG_ENGINE_SIM_LABELED_GAUGE_H\n#define ATG_ENGINE_SIM_LABELED_GAUGE_H\n\n#include \"ui_element.h\"\n\n#include \"gauge.h\"\n\n#include <string>\n\nclass LabeledGauge : public UiElement {\n    public:\n        LabeledGauge();\n        virtual ~LabeledGauge();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n\n        Gauge *m_gauge;\n        std::string m_title;\n\n        int m_precision;\n        bool m_spaceBeforeUnit;\n        std::string m_unit;\n\n        float m_margin = 10.0f;\n        float m_needleInnerRadius = 0.1f;\n        float m_needleOuterRadius = 0.7f;\n};\n\n#endif /* ATG_ENGINE_SIM_LABELED_GAUGE_H */\n"
  },
  {
    "path": "include/leveling_filter.h",
    "content": "#ifndef ATG_ENGINE_SIM_LEVELING_FILTER_H\n#define ATG_ENGINE_SIM_LEVELING_FILTER_H\n\n#include \"filter.h\"\n\n#include \"function.h\"\n\nclass LevelingFilter : public Filter {\n    public:\n        LevelingFilter();\n        virtual ~LevelingFilter();\n\n        virtual float f(float sample);\n        float getAttenuation() const { return m_attenuation; }\n\n    protected:\n        float m_peak;\n        float m_attenuation;\n\n    public:\n        float p_maxLevel;\n        float p_minLevel;\n        float p_target;\n};\n\n#endif /* ATG_ENGINE_SIM_LEVELING_FILTER_H */\n"
  },
  {
    "path": "include/load_simulation_cluster.h",
    "content": "#ifndef ATG_ENGINE_SIM_LOAD_SIMULATION_CLUSTER_H\n#define ATG_ENGINE_SIM_LOAD_SIMULATION_CLUSTER_H\n\n#include \"ui_element.h\"\n\n#include \"simulator.h\"\n#include \"labeled_gauge.h\"\n\nclass LoadSimulationCluster : public UiElement {\n    public:\n        LoadSimulationCluster();\n        virtual ~LoadSimulationCluster();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n        void setUnits();\n\n        void setSimulator(Simulator *simulator) { m_simulator = simulator; }\n\n    private:\n        Transmission *getTransmission() const { return m_simulator->getTransmission(); }\n\n    protected:\n        void drawCurrentGear(const Bounds &bounds);\n        void drawClutchPressureGauge(const Bounds &bounds);\n        void drawSystemStatus(const Bounds &bounds);\n        void updateHpAndTorque(float dt);\n        bool isIgnitionOn() const;\n\n        float m_systemStatusLights[4];\n        LabeledGauge *m_dynoSpeedGauge;\n        LabeledGauge *m_torqueGauge;\n        LabeledGauge *m_hpGauge;\n        LabeledGauge *m_clutchPressureGauge;\n\n        double m_filteredHorsepower;\n        double m_filteredTorque;\n\n        double m_peakHorsepowerRpm;\n        double m_peakHorsepower;\n        double m_peakTorqueRpm;\n        double m_peakTorque;\n        \n        std::string m_powerUnits;\n        std::string m_torqueUnits;\n\n        Simulator *m_simulator;\n};\n\n#endif /* ATG_ENGINE_SIM_LOAD_SIMULATION_CLUSTER_H */\n"
  },
  {
    "path": "include/low_pass_filter.h",
    "content": "#ifndef ATG_ENGINE_SIM_LOW_PASS_FILTER_H\n#define ATG_ENGINE_SIM_LOW_PASS_FILTER_H\n\n#include \"filter.h\"\n\n#include \"constants.h\"\n\nclass LowPassFilter : public Filter {\n    public:\n        LowPassFilter();\n        virtual ~LowPassFilter();\n\n        virtual float f(float sample) override;\n\n        __forceinline float fast_f(float sample) {\n            const float alpha = m_dt / (m_rc + m_dt);\n            m_y = alpha * sample + (1 - alpha) * m_y;\n\n            return m_y;\n        }\n\n        inline void setCutoffFrequency(float f) {\n            m_rc = 1.0f / (f * 2.0f * static_cast<float>(constants::pi));\n        }\n\n        float m_dt;\n\n    protected:\n        float m_y;\n        float m_rc;\n};\n\n#endif /* ATG_ENGINE_SIM_LOW_PASS_FILTER_H */\n"
  },
  {
    "path": "include/mixer_cluster.h",
    "content": "#ifndef ATG_ENGINE_SIM_MIXER_CLUSTER_H\n#define ATG_ENGINE_SIM_MIXER_CLUSTER_H\n\n#include \"ui_element.h\"\n\n#include \"engine.h\"\n#include \"gauge.h\"\n#include \"cylinder_temperature_gauge.h\"\n#include \"cylinder_pressure_gauge.h\"\n#include \"labeled_gauge.h\"\n#include \"throttle_display.h\"\n#include \"simulator.h\"\n\nclass MixerCluster : public UiElement {\n    public:\n        MixerCluster();\n        virtual ~MixerCluster();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n\n        void setSimulator(Simulator *simulator) { m_simulator = simulator; }\n\n    protected:\n        Simulator *m_simulator;\n\n    protected:\n        LabeledGauge\n            *m_volumeGauge,\n            *m_convolutionGauge,\n            *m_highFreqFilterGauge,\n            *m_levelerGauge,\n            *m_noise0Gauge,\n            *m_noise1Gauge;\n};\n\n#endif /* ATG_ENGINE_SIM_MIXER_CLUSTER_H */\n"
  },
  {
    "path": "include/oscilloscope.h",
    "content": "#ifndef ATG_ENGINE_SIM_OSCILLOSCOPE_H\n#define ATG_ENGINE_SIM_OSCILLOSCOPE_H\n\n#include \"ui_element.h\"\n\nclass Oscilloscope : public UiElement {\n    public:\n        struct DataPoint {\n            double x, y;\n        };\n\n    public:\n        Oscilloscope();\n        virtual ~Oscilloscope();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n        void render(const Bounds &bounds);\n\n        Point dataPointToRenderPosition(\n            const DataPoint &p,\n            const Bounds &bounds) const;\n\n        void addDataPoint(double x, double y);\n\n        void setBufferSize(int n);\n        void reset();\n\n        double m_xMin;\n        double m_xMax;\n\n        double m_yMin;\n        double m_yMax;\n\n        double m_dynamicallyResizeX;\n        double m_dynamicallyResizeY;\n\n        double m_lineWidth;\n        bool m_drawReverse;\n        bool m_drawZero;\n\n        ysVector i_color;\n\n    protected:\n        DataPoint *m_points;\n        Point *m_renderBuffer;\n        int m_writeIndex;\n        int m_bufferSize;\n        int m_pointCount;\n};\n\n#endif /* ATG_ENGINE_SIM_OSCILLOSCOPE_H */\n"
  },
  {
    "path": "include/oscilloscope_cluster.h",
    "content": "#ifndef ATG_ENGINE_SIM_OSCILLOSCOPE_CLUSTER_H\n#define ATG_ENGINE_SIM_OSCILLOSCOPE_CLUSTER_H\n\n#include \"ui_element.h\"\n\n#include \"simulator.h\"\n#include \"oscilloscope.h\"\n\nclass OscilloscopeCluster : public UiElement {\n    private:\n        static constexpr int MaxLayeredScopes = 5;\n\n    public:\n        OscilloscopeCluster();\n        virtual ~OscilloscopeCluster();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n        virtual void signal(UiElement *element, Event event);\n\n        virtual void update(float dt);\n        virtual void render();\n\n        void sample();\n        void setSimulator(Simulator *simulator);\n\n        Oscilloscope *getTotalExhaustFlowOscilloscope() const { return m_totalExhaustFlowScope; }\n        Oscilloscope *getExhaustFlowOscilloscope() const { return m_exhaustFlowScope; }\n        Oscilloscope *getIntakeFlowOscilloscope() const { return m_intakeFlowScope; }\n        Oscilloscope *getAudioWaveformOscilloscope() const { return m_audioWaveformScope; }\n        Oscilloscope *getIntakeValveLiftOscilloscope() const { return m_intakeValveLiftScope; }\n        Oscilloscope *getExhaustValveLiftOscilloscope() const { return m_exhaustValveLiftScope; }\n        Oscilloscope *getCylinderPressureScope() const { return m_cylinderPressureScope; }\n        Oscilloscope *getSparkAdvanceScope() const { return m_sparkAdvanceScope; }\n        Oscilloscope *getCylinderMoleculesScope() const { return m_cylinderMoleculesScope; }\n        Oscilloscope *getPvScope() const { return m_pvScope; }\n        void setDynoMaxRange(double redline) { m_torqueScope->m_xMax = redline + 500; m_powerScope->m_xMax = redline + 500; }\n\n    protected:\n        void renderScope(\n            Oscilloscope *osc,\n            const Bounds &bounds,\n            const std::string &title,\n            bool overlay=false);\n\n        Simulator *m_simulator;\n        Oscilloscope\n            *m_torqueScope,\n            *m_powerScope,\n            *m_audioWaveformScope,\n            *m_exhaustValveLiftScope,\n            *m_intakeValveLiftScope,\n            *m_cylinderPressureScope,\n            *m_totalExhaustFlowScope,\n            *m_exhaustFlowScope,\n            *m_intakeFlowScope,\n            *m_cylinderMoleculesScope,\n            *m_sparkAdvanceScope,\n            *m_pvScope,\n            *m_currentFocusScopes[MaxLayeredScopes];\n        float m_updatePeriod;\n        float m_updateTimer;\n\n        double m_torque;\n        double m_power;\n\n        std::string m_powerUnits;\n        std::string m_torqueUnits;\n\n};\n\n#endif /* ATG_ENGINE_SIM_OSCILLOSCOPE_CLUSTER_H */\n"
  },
  {
    "path": "include/part.h",
    "content": "#ifndef ATG_ENGINE_SIM_PART_H\n#define ATG_ENGINE_SIM_PART_H\n\n#include \"scs.h\"\n\nclass Part {\n    public:\n        Part();\n        virtual ~Part();\n\n        virtual void destroy();\n\n        atg_scs::RigidBody m_body;\n};\n\n#endif /* ATG_ENGINE_SIM_PART_H */\n"
  },
  {
    "path": "include/performance_cluster.h",
    "content": "#ifndef ATG_ENGINE_SIM_PERFORMANCE_CLUSTER_H\n#define ATG_ENGINE_SIM_PERFORMANCE_CLUSTER_H\n\n#include \"ui_element.h\"\n\n#include \"engine.h\"\n#include \"gauge.h\"\n#include \"cylinder_temperature_gauge.h\"\n#include \"cylinder_pressure_gauge.h\"\n#include \"labeled_gauge.h\"\n#include \"throttle_display.h\"\n#include \"simulator.h\"\n\nclass PerformanceCluster : public UiElement {\n    public:\n        PerformanceCluster();\n        virtual ~PerformanceCluster();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n\n        void setSimulator(Simulator *simulator) { m_simulator = simulator; }\n        void addTimePerTimestepSample(double sample);\n        void addAudioLatencySample(double sample);\n        void addInputBufferUsageSample(double sample);\n\n        LabeledGauge *m_timePerTimestepGauge;\n        LabeledGauge *m_fpsGauge;\n        LabeledGauge *m_simSpeedGauge;\n        LabeledGauge *m_simulationFrequencyGauge;\n        LabeledGauge *m_inputSamplesGauge;\n        LabeledGauge *m_audioLagGauge;\n\n    protected:\n        double m_timePerTimestep;\n\n        double m_filteredSimulationFrequency;\n        double m_audioLatency;\n        double m_inputBufferUsage;\n\n        Simulator *m_simulator;\n};\n\n#endif /* ATG_ENGINE_SIM_PERFORMANCE_CLUSTER_H */\n"
  },
  {
    "path": "include/piston.h",
    "content": "#ifndef ATG_ENGINE_SIM_PISTON_H\n#define ATG_ENGINE_SIM_PISTON_H\n\n#include \"part.h\"\n\nclass ConnectingRod;\nclass CylinderBank;\nclass Piston : public Part {\n    public:\n        struct Parameters {\n            ConnectingRod *Rod;\n            CylinderBank *Bank;\n            int CylinderIndex;\n\n            double BlowbyFlowCoefficient;\n            double CompressionHeight;\n            double WristPinPosition;\n            double Displacement;\n            double mass;\n        };\n\n    public:\n        Piston();\n        virtual ~Piston();\n\n        void initialize(const Parameters &params);\n        inline void setCylinderConstraint(atg_scs::LineConstraint *constraint);\n        virtual void destroy();\n\n        double relativeX() const;\n        double relativeY() const;\n\n        double calculateCylinderWallForce() const;\n        inline ConnectingRod *getRod() const { return m_rod; }\n        inline CylinderBank *getCylinderBank() const { return m_bank; }\n        inline int getCylinderIndex() const { return m_cylinderIndex; }\n        inline double getCompressionHeight() const { return m_compressionHeight; }\n        inline double getDisplacement() const { return m_displacement; }\n        inline double getWristPinLocation() const { return m_wristPinLocation; }\n        inline double getMass() const { return m_mass; }\n        inline double getBlowbyK() const { return m_blowby_k; }\n\n    protected:\n        ConnectingRod *m_rod;\n        CylinderBank *m_bank;\n        atg_scs::LineConstraint *m_cylinderConstraint;\n        int m_cylinderIndex;\n        double m_compressionHeight;\n        double m_displacement;\n        double m_wristPinLocation;\n        double m_mass;\n        double m_blowby_k;\n};\n\nvoid Piston::setCylinderConstraint(atg_scs::LineConstraint *constraint) {\n    m_cylinderConstraint = constraint;\n}\n\n#endif /* ATG_ENGINE_SIM_PISTON_H */\n"
  },
  {
    "path": "include/piston_engine_simulator.h",
    "content": "#ifndef ATG_ENGINE_SIM_PISTON_ENGINE_SIMULATOR_H\n#define ATG_ENGINE_SIM_PISTON_ENGINE_SIMULATOR_H\n\n#include \"simulator.h\"\n\n#include \"engine.h\"\n#include \"transmission.h\"\n#include \"combustion_chamber.h\"\n#include \"vehicle.h\"\n#include \"synthesizer.h\"\n#include \"dynamometer.h\"\n#include \"starter_motor.h\"\n#include \"derivative_filter.h\"\n#include \"vehicle_drag_constraint.h\"\n#include \"delay_filter.h\"\n\n#include \"scs.h\"\n\n#include <chrono>\n\nclass PistonEngineSimulator : public Simulator {\n    public:\n        PistonEngineSimulator();\n        virtual ~PistonEngineSimulator() override;\n\n        void loadSimulation(Engine *engine, Vehicle *vehicle, Transmission *transmission);\n\n        virtual double getTotalExhaustFlow() const;\n        void endFrame();\n        virtual void destroy() override;\n\n        void setFluidSimulationSteps(int steps) { m_fluidSimulationSteps = steps; }\n        int getFluidSimulationSteps() const { return m_fluidSimulationSteps; }\n        int getFluidSimulationFrequency() const { return m_fluidSimulationSteps * getSimulationFrequency(); }\n\n        virtual double getAverageOutputSignal() const override;\n\n        DerivativeFilter m_derivativeFilter;\n\n    protected:\n        virtual void simulateStep_() override;\n\n    protected:\n        void placeAndInitialize();\n        void placeCylinder(int i);\n        \n    protected:\n        virtual void writeToSynthesizer() override;\n\n    protected:\n        DelayFilter *m_delayFilters;\n\n        atg_scs::FixedPositionConstraint *m_crankConstraints;\n        atg_scs::ClutchConstraint *m_crankshaftLinks;\n        atg_scs::RotationFrictionConstraint *m_crankshaftFrictionConstraints;\n        atg_scs::LineConstraint *m_cylinderWallConstraints;\n        atg_scs::LinkConstraint *m_linkConstraints;\n        atg_scs::RigidBody m_vehicleMass;\n        VehicleDragConstraint m_vehicleDrag;\n\n        std::chrono::steady_clock::time_point m_simulationStart;\n        std::chrono::steady_clock::time_point m_simulationEnd;\n\n        Engine *m_engine;\n        Transmission *m_transmission;\n        Vehicle *m_vehicle;\n\n        double *m_exhaustFlowStagingBuffer;\n\n        int m_fluidSimulationSteps;\n};\n\n#endif /* ATG_ENGINE_SIM_PISTON_ENGINE_SIMULATOR_H */\n"
  },
  {
    "path": "include/piston_object.h",
    "content": "#ifndef ATG_ENGINE_SIM_PISTON_OBJECT_H\n#define ATG_ENGINE_SIM_PISTON_OBJECT_H\n\n#include \"simulation_object.h\"\n\n#include \"piston.h\"\n#include \"geometry_generator.h\"\n\nclass PistonObject : public SimulationObject {\n    public:\n        PistonObject();\n        virtual ~PistonObject();\n\n        virtual void generateGeometry();\n        virtual void render(const ViewParameters *view);\n        virtual void process(float dt);\n        virtual void destroy();\n\n        Piston *m_piston;\n\n    protected:\n        GeometryGenerator::GeometryIndices\n            m_wristPinHole;\n};\n\n#endif /* ATG_ENGINE_SIM_PISTON_OBJECT_H */\n"
  },
  {
    "path": "include/preemphasis_filter.h",
    "content": "#ifndef ATG_ENGINE_SIM_PREEMPHASIS_FILTER_H\n#define ATG_ENGINE_SIM_PREEMPHASIS_FILTER_H\n\n#include \"filter.h\"\n\n#include \"low_pass_filter.h\"\n\n#include <random>\n\nclass PreemphasisFilter : public Filter {\npublic:\n    PreemphasisFilter() { m_lastSample = 0; }\n    virtual ~PreemphasisFilter() {}\n\n    virtual float f(float sample) override { return fast_f(sample); }\n\n    __forceinline float fast_f(float sample) {\n        const float s = -0.95f * sample + m_lastSample;\n\n        m_lastSample = sample;\n        return s;\n    }\n\nprotected:\n    float m_lastSample;\n};\n\n#endif /* ATG_ENGINE_SIM_PREEMPHASIS_FILTER_H */\n"
  },
  {
    "path": "include/right_gauge_cluster.h",
    "content": "#ifndef ATG_ENGINE_SIM_RIGHT_GAUGE_CLUSTER_H\n#define ATG_ENGINE_SIM_RIGHT_GAUGE_CLUSTER_H\n\n#include \"ui_element.h\"\n\n#include \"engine.h\"\n#include \"simulator.h\"\n#include \"gauge.h\"\n#include \"firing_order_display.h\"\n#include \"labeled_gauge.h\"\n#include \"throttle_display.h\"\n#include \"afr_cluster.h\"\n#include \"fuel_cluster.h\"\n\nclass RightGaugeCluster : public UiElement {\n    public:\n        RightGaugeCluster();\n        virtual ~RightGaugeCluster();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n\n        void setEngine(Engine *engine);\n        void setUnits();\n        double getManifoldPressureWithUnits(double ambientPressure);\n\n        Simulator *m_simulator;\n\n    private:\n        double getRpm() const;\n        double getRedline() const;\n        double getSpeed() const;\n        double getManifoldPressure() const;\n\n    protected:\n        Engine *m_engine;\n\n        void renderTachSpeedCluster(const Bounds &bounds);\n        void renderFuelAirCluster(const Bounds &bounds);\n\n        LabeledGauge *m_tachometer;\n        LabeledGauge *m_speedometer;\n        LabeledGauge *m_manifoldVacuumGauge;\n        LabeledGauge *m_intakeCfmGauge;\n        LabeledGauge *m_volumetricEffGauge;\n        FuelCluster *m_fuelCluster;\n        ThrottleDisplay *m_throttleDisplay;\n        AfrCluster *m_afrCluster;\n        FiringOrderDisplay *m_combusionChamberStatus;\n        std::string m_speedUnits;\n        std::string m_pressureUnits;\n        bool m_isAbsolute;\n};\n\n#endif /* ATG_ENGINE_SIM_GAUGE_CLUSTER_H */\n"
  },
  {
    "path": "include/ring_buffer.h",
    "content": "#ifndef ATG_ENGINE_SIM_RING_BUFFER_H\n#define ATG_ENGINE_SIM_RING_BUFFER_H\n\n#include \"part.h\"\n\n#include <cstring>\n\ntemplate <typename T_Data>\nclass RingBuffer {\npublic:\n    RingBuffer() {\n        m_buffer = nullptr;\n        m_capacity = 0;\n        m_writeIndex = 0;\n        m_start = 0;\n    }\n\n    ~RingBuffer() {\n        destroy();\n    }\n\n    void initialize(size_t capacity) {\n        m_buffer = new T_Data[capacity];\n        m_capacity = capacity;\n        m_writeIndex = 0;\n        m_start = 0;\n    }\n\n    void destroy() {\n        if (m_buffer != nullptr) {\n            delete[] m_buffer;\n            m_buffer = nullptr;\n        }\n\n        m_capacity = 0;\n        m_writeIndex = 0;\n        m_start = 0;\n    }\n\n    inline void write(T_Data data) {\n        m_buffer[m_writeIndex] = data;\n\n        if (++m_writeIndex >= m_capacity) {\n            m_writeIndex = 0;\n        }\n    }\n\n    inline void overwrite(T_Data data, size_t index) {\n        if (start + index < m_capacity) {\n            m_buffer[m_start + index] = data;\n        }\n        else {\n            m_buffer[m_start + index - m_capacity] = data;\n        }\n    }\n\n    inline size_t index(size_t base, int offset) {\n        if (offset == 0) return base;\n        else if (offset < 0) {\n            const size_t offset_u = -offset;\n            if (offset_u <= base) return base - offset_u;\n            else return (base + m_capacity) - offset_u;\n        }\n        else {\n            const size_t offset_u = offset;\n            const size_t rawOffset = base + offset_u;\n            if (rawOffset >= m_capacity) return rawOffset - m_capacity;\n            else return rawOffset;\n        }\n    }\n\n    inline T_Data read(size_t index) const {\n        return (m_start + index) >= m_capacity\n            ? m_buffer[m_start + index - m_capacity]\n            : m_buffer[m_start + index];\n    }\n\n    inline void read(size_t n, T_Data *target) {\n        if (m_start + n < m_capacity) {\n            memcpy(target, m_buffer + m_start, n * sizeof(T_Data));\n        }\n        else {\n            memcpy(\n                target,\n                m_buffer + m_start,\n                (m_capacity - m_start) * sizeof(T_Data));\n            memcpy(\n                target + (m_capacity - m_start),\n                m_buffer,\n                (n - (m_capacity - m_start)) * sizeof(T_Data));\n        }\n    }\n\n    inline void readAndRemove(size_t n, T_Data *target) {\n        if (m_start + n < m_capacity) {\n            memcpy(target, m_buffer + m_start, n * sizeof(T_Data));\n        }\n        else {\n            memcpy(\n                target,\n                m_buffer + m_start,\n                (m_capacity - m_start) * sizeof(T_Data));\n            memcpy(\n                target + (m_capacity - m_start),\n                m_buffer,\n                (n - (m_capacity - m_start)) * sizeof(T_Data));\n        }\n\n        m_start += n;\n        if (m_start >= m_capacity) {\n            m_start -= m_capacity;\n        }\n    }\n\n    inline void setWriteIndex(size_t writeIndex) {\n        m_writeIndex = writeIndex;\n    }\n\n    inline void removeBeginning(size_t n) {\n        m_start += n;\n        if (m_start >= m_capacity) {\n            m_start -= m_capacity;\n        }\n    }\n\n    inline void setStartIndex(size_t startIndex) {\n        m_start = startIndex;\n    }\n\n    inline size_t size() const {\n        return (m_writeIndex < m_start)\n            ? m_writeIndex + (m_capacity - m_start)\n            : m_writeIndex - m_start;\n    }\n\n    inline size_t writeIndex() const {\n        return m_writeIndex;\n    }\n\n    inline size_t start() const {\n        return m_start;\n    }\n\nprivate:\n    T_Data *m_buffer;\n    size_t m_capacity;\n    size_t m_writeIndex;\n    size_t m_start;\n};\n\n#endif /* ATG_ENGINE_SIM_RING_BUFFER_H */\n"
  },
  {
    "path": "include/scs.h",
    "content": "#ifndef ATG_ENGINE_SIM_SCS_H\n#define ATG_ENGINE_SIM_SCS_H\n\n#include \"../dependencies/submodules/simple-2d-constraint-solver/include/scs.h\"\n\n#endif /* ATG_ENGINE_SIM_SCS_H */\n"
  },
  {
    "path": "include/shaders.h",
    "content": "#ifndef ATG_ENGINE_SIM_SHADERS_H\n#define ATG_ENGINE_SIM_SHADERS_H\n\n#include \"delta.h\"\n\n#include \"ui_math.h\"\n\nclass Shaders : public dbasic::ShaderBase {\n    public:\n        Shaders();\n        ~Shaders();\n\n        ysError Initialize(\n                dbasic::ShaderSet *shaderSet,\n                ysRenderTarget *mainRenderTarget,\n                ysRenderTarget *uiRenderTarget,\n                ysShaderProgram *shaderProgram,\n                ysInputLayout *inputLayout);\n        virtual ysError UseMaterial(dbasic::Material *material);\n\t\tvirtual void SetObjectTransform(const ysMatrix &mat);\n\t\tvirtual void ConfigureModel(float scale, dbasic::ModelAsset *model);\n\n        void SetBaseColor(const ysVector &color);\n        void ResetBaseColor();\n\n        dbasic::StageEnableFlags GetRegularFlags() const;\n        dbasic::StageEnableFlags GetUiFlags() const;\n\n        void CalculateCamera(\n            float width,\n            float height,\n            const Bounds &cameraBounds,\n            float screenWidth,\n            float screenHeight,\n            float angle = 0.0f);\n        void CalculateUiCamera(float screenWidth, float screenHeight);\n\n        void SetClearColor(const ysVector &col);\n\n    public:\n        dbasic::ShaderScreenVariables m_screenVariables;\n        dbasic::ShaderScreenVariables m_uiScreenVariables;\n        dbasic::ShaderObjectVariables m_objectVariables;\n\n        ysVector m_cameraPosition;\n\n    protected:\n        dbasic::ShaderStage *m_mainStage;\n        dbasic::ShaderStage *m_uiStage;\n\n        dbasic::LightingControls m_lightingControls;\n};\n\n#endif /* ATG_ENGINE_SIM_SHADERS_H */\n"
  },
  {
    "path": "include/simulation_object.h",
    "content": "#ifndef ATG_ENGINE_SIM_SIMULATION_OBJECT_H\n#define ATG_ENGINE_SIM_SIMULATION_OBJECT_H\n\n#include \"scs.h\"\n#include \"delta.h\"\n\nclass Piston;\nclass CylinderBank;\nclass EngineSimApplication;\nclass SimulationObject {\n    public:\n        struct ViewParameters {\n            int Layer0;\n            int Layer1;\n            int Sublayer;\n        };\n\n    public:\n        SimulationObject();\n        virtual ~SimulationObject();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void generateGeometry();\n        virtual void render(const ViewParameters *settings);\n        virtual void process(float dt);\n        virtual void destroy();\n\n        Piston *getForemostPiston(CylinderBank *bank, int layer);\n\n    protected:\n        void resetShader();\n        void setTransform(\n            atg_scs::RigidBody *rigidBody,\n            float scale = 1.0f,\n            float lx = 0.0f,\n            float ly = 0.0f,\n            float theta = 0.0f,\n            float z = 0.0f);\n        ysVector tintByLayer(const ysVector &col, int layers) const;\n\n        EngineSimApplication *m_app;\n};\n\n#endif /* ATG_ENGINE_SIM_SIMULATION_OBJECT_H */\n"
  },
  {
    "path": "include/simulator.h",
    "content": "#ifndef ATG_ENGINE_SIM_SIMULATOR_H\n#define ATG_ENGINE_SIM_SIMULATOR_H\n\n#include \"engine.h\"\n#include \"transmission.h\"\n#include \"vehicle.h\"\n#include \"synthesizer.h\"\n#include \"dynamometer.h\"\n#include \"starter_motor.h\"\n#include \"derivative_filter.h\"\n#include \"vehicle_drag_constraint.h\"\n#include \"delay_filter.h\"\n#include \"engine.h\"\n\n#include <chrono>\n\nclass Simulator {\npublic:\n    enum class SystemType {\n        NsvOptimized,\n        Generic\n    };\n\n    struct Parameters {\n        SystemType systemType = SystemType::NsvOptimized;\n    };\n\n    static constexpr int DynoTorqueSamples = 512;\n\npublic:\n    Simulator();\n    virtual ~Simulator();\n\n    virtual void initialize(const Parameters &params);\n    void loadSimulation(Engine *engine, Vehicle *vehicle, Transmission *transmission);\n    void releaseSimulation();\n\n    virtual void startFrame(double dt);\n    bool simulateStep();\n    virtual double getTotalExhaustFlow() const;\n    int readAudioOutput(int samples, int16_t *target);\n    virtual void endFrame();\n    virtual void destroy();\n\n    void startAudioRenderingThread();\n    void endAudioRenderingThread();\n\n    int getFrameIterationCount() const { return m_steps; }\n\n    Synthesizer &synthesizer() { return m_synthesizer; }\n\n    Engine *getEngine() const { return m_engine; }\n    Transmission *getTransmission() const { return m_transmission; }\n    Vehicle *getVehicle() const { return m_vehicle; }\n    atg_scs::RigidBodySystem *getSystem() { return m_system; }\n\n    void setSimulationFrequency(int frequency) { m_simulationFrequency = frequency; }\n    int getSimulationFrequency() const { return m_simulationFrequency; }\n\n    double getTimestep() const { return 1.0 / m_simulationFrequency; }\n\n    void setTargetSynthesizerLatency(double latency) { m_targetSynthesizerLatency = latency; }\n    double getTargetSynthesizerLatency() const { return m_targetSynthesizerLatency; }\n    double getSynthesizerInputLatency() const { return m_synthesizer.getLatency(); }\n    double getSynthesizerInputLatencyTarget() const;\n\n    void setSimulationSpeed(double simSpeed) { m_simulationSpeed = simSpeed; }\n    double getSimulationSpeed() const { return m_simulationSpeed; }\n    int getCurrentIteration() const { return m_currentIteration; }\n    double getAverageProcessingTime() const { return m_physicsProcessingTime; }\n\n    int simulationSteps() const { return m_steps; }\n\n    virtual double getFilteredDynoTorque() const;\n    virtual double getDynoPower() const;\n    virtual double getAverageOutputSignal() const;\n\n    double filteredEngineSpeed() const { return m_filteredEngineSpeed; }\n\n    Dynamometer m_dyno;\n    StarterMotor m_starterMotor;\n\nprotected:\n    void initializeSynthesizer();\n    virtual void simulateStep_();\n    virtual void writeToSynthesizer() = 0;\n\n    atg_scs::RigidBodySystem *m_system;\n\nprivate:\n    void updateFilteredEngineSpeed(double dt);\n\nprivate:\n    atg_scs::RigidBody m_vehicleMass;\n    VehicleDragConstraint m_vehicleDrag;\n\n    Synthesizer m_synthesizer;\n\n    std::chrono::steady_clock::time_point m_simulationStart;\n    std::chrono::steady_clock::time_point m_simulationEnd;\n    int m_currentIteration;\n\n    Engine *m_engine;\n    Transmission *m_transmission;\n    Vehicle *m_vehicle;\n\n    double m_physicsProcessingTime;\n\n    int m_simulationFrequency;\n\n    double m_targetSynthesizerLatency;\n    double m_simulationSpeed;\n\n    double *m_dynoTorqueSamples;\n    int m_lastDynoTorqueSample;\n\n    double m_filteredEngineSpeed;\n\n    int m_steps;\n};\n\n#endif /* ATG_ENGINE_SIM_SIMULATOR_H */\n"
  },
  {
    "path": "include/standard_valvetrain.h",
    "content": "#ifndef ATG_ENGINE_SIM_STANDARD_VALVETRAIN_H\n#define ATG_ENGINE_SIM_STANDARD_VALVETRAIN_H\n\n#include \"valvetrain.h\"\n\nclass StandardValvetrain : public Valvetrain {\npublic:\n    struct Parameters {\n        Camshaft *intakeCamshaft;\n        Camshaft *exhaustCamshaft;\n    };\n\npublic:\n    StandardValvetrain();\n    virtual ~StandardValvetrain();\n\n    void initialize(const Parameters &parameters);\n\n    virtual double intakeValveLift(int cylinder) override;\n    virtual double exhaustValveLift(int cylinder) override;\n\n    virtual Camshaft *getActiveIntakeCamshaft() override;\n    virtual Camshaft *getActiveExhaustCamshaft() override;\n\nprivate:\n    Camshaft *m_intakeCamshaft;\n    Camshaft *m_exhaustCamshaft;\n};\n\n#endif /* ATG_ENGINE_SIM_STANDARD_VALVETRAIN_H */\n"
  },
  {
    "path": "include/starter_motor.h",
    "content": "#ifndef ATG_ENGINE_SIM_STARTER_MOTOR_H\n#define ATG_ENGINE_SIM_STARTER_MOTOR_H\n\n#include \"scs.h\"\n\n#include \"crankshaft.h\"\n\nclass StarterMotor : public atg_scs::Constraint {\npublic:\n    StarterMotor();\n    virtual ~StarterMotor();\n\n    void connectCrankshaft(Crankshaft *crankshaft);\n    virtual void calculate(Output *output, atg_scs::SystemState *state);\n\n    double m_ks;\n    double m_kd;\n    double m_maxTorque;\n    double m_rotationSpeed;\n    bool m_enabled;\n};\n\n#endif /* ATG_ENGINE_SIM_STARTER_MOTOR_H */\n"
  },
  {
    "path": "include/synthesizer.h",
    "content": "#ifndef ATG_ENGINE_SIM_ENGINE_SYNTHESIZER_H\n#define ATG_ENGINE_SIM_ENGINE_SYNTHESIZER_H\n\n#include \"convolution_filter.h\"\n#include \"leveling_filter.h\"\n#include \"derivative_filter.h\"\n#include \"low_pass_filter.h\"\n#include \"jitter_filter.h\"\n#include \"ring_buffer.h\"\n#include \"butterworth_low_pass_filter.h\"\n\n#include <cinttypes>\n#include <thread>\n#include <mutex>\n#include <atomic>\n#include <condition_variable>\n\nclass Synthesizer {\n    public:\n        struct AudioParameters {\n            float volume = 1.0f;\n            float convolution = 1.0f;\n            float dF_F_mix = 0.01f;\n            float inputSampleNoise = 0.5f;\n            float inputSampleNoiseFrequencyCutoff = 10000.0f;\n            float airNoise = 1.0f;\n            float airNoiseFrequencyCutoff = 2000.0f;\n            float levelerTarget = 30000.0f;\n            float levelerMaxGain = 1.9f;\n            float levelerMinGain = 0.00001f;\n        };\n\n        struct Parameters {\n            int inputChannelCount = 1;\n            int inputBufferSize = 1024;\n            int audioBufferSize = 44100;\n            float inputSampleRate = 10000;\n            float audioSampleRate = 44100;\n            AudioParameters initialAudioParameters;\n        };\n\n        struct InputChannel {\n            RingBuffer<float> data;\n            float *transferBuffer = nullptr;\n            double lastInputSample = 0.0f;\n        };\n\n        struct ProcessingFilters {\n            ConvolutionFilter convolution;\n            DerivativeFilter derivative;\n            JitterFilter jitterFilter;\n            ButterworthLowPassFilter<float> airNoiseLowPass;\n            LowPassFilter inputDcFilter;\n            ButterworthLowPassFilter<double> antialiasing;\n        };\n\n    public:\n        Synthesizer();\n        ~Synthesizer();\n\n        void initialize(const Parameters &p);\n        void initializeImpulseResponse(\n            const int16_t *impulseResponse,\n            unsigned int samples,\n            float volume,\n            int index);\n        void startAudioRenderingThread();\n        void endAudioRenderingThread();\n        void destroy();\n\n        int readAudioOutput(int samples, int16_t *buffer);\n\n        void writeInput(const double *data);\n        void endInputBlock();\n\n        void waitProcessed();\n\n        void audioRenderingThread();\n        void renderAudio();\n\n        double getLatency() const;\n\n        int inputDelta(int s1, int s0) const;\n        double inputDistance(double s1, double s0) const;\n\n        void setInputSampleRate(double sampleRate);\n        double getInputSampleRate() const { return m_inputSampleRate; }\n\n        int16_t renderAudio(int inputOffset);\n\n        double getLevelerGain();\n        AudioParameters getAudioParameters();\n        void setAudioParameters(const AudioParameters &params);\n\n    //protected:\n        ButterworthLowPassFilter<float> m_antialiasing;\n        LevelingFilter m_levelingFilter;\n        InputChannel *m_inputChannels;\n        AudioParameters m_audioParameters;\n        int m_inputChannelCount;\n        int m_inputBufferSize;\n        int m_inputSamplesRead;\n        int m_latency;\n        double m_inputWriteOffset;\n        double m_lastInputSampleOffset;\n\n        RingBuffer<int16_t> m_audioBuffer;\n        int m_audioBufferSize;\n\n        float m_inputSampleRate;\n        float m_audioSampleRate;\n\n        std::thread *m_thread;\n        std::atomic<bool> m_run;\n        bool m_processed;\n\n        std::mutex m_inputLock;\n        std::mutex m_lock0;\n        std::condition_variable m_cv0;\n\n        ProcessingFilters *m_filters;\n};\n\n#endif /* ATG_ENGINE_SIM_ENGINE_SYNTHESIZER_H */\n"
  },
  {
    "path": "include/throttle.h",
    "content": "#ifndef ATG_ENGINE_SIM_THROTTLE_H\n#define ATG_ENGINE_SIM_THROTTLE_H\n\n#include \"part.h\"\n\nclass Engine;\nclass Throttle {\npublic:\n    Throttle();\n    virtual ~Throttle();\n\n    virtual void setSpeedControl(double s);\n    virtual void update(double dt, Engine *engine);\n\n    inline double getSpeedControl() const { return m_speedControl; }\n\nprotected:\n    double m_speedControl;\n};\n\n#endif /* ATG_ENGINE_SIM_THROTTLE_H */\n"
  },
  {
    "path": "include/throttle_display.h",
    "content": "#ifndef ATG_ENGINE_SIM_THROTTLE_DISPLAY_H\n#define ATG_ENGINE_SIM_THROTTLE_DISPLAY_H\n\n#include \"ui_element.h\"\n\n#include \"engine.h\"\n#include \"geometry_generator.h\"\n\nclass ThrottleDisplay : public UiElement {\n    public:\n        ThrottleDisplay();\n        virtual ~ThrottleDisplay();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n\n        Engine *m_engine;\n\n    protected:\n        void renderThrottle(const Bounds &bounds);\n        void renderSpeedControl(const Bounds &bounds);\n};\n\n#endif /* ATG_ENGINE_SIM_THROTTLE_DISPLAY_H */\n"
  },
  {
    "path": "include/transmission.h",
    "content": "#ifndef ATG_ENGINE_SIM_TRANSMISSION_H\n#define ATG_ENGINE_SIM_TRANSMISSION_H\n\n#include \"vehicle.h\"\n#include \"engine.h\"\n#include \"scs.h\"\n\nclass Transmission {\n    public:\n        struct Parameters {\n            int GearCount;\n            const double *GearRatios;\n            double MaxClutchTorque;\n        };\n\n    public:\n        Transmission();\n        ~Transmission();\n\n        void initialize(const Parameters &params);\n        void update(double dt);\n        void addToSystem(\n            atg_scs::RigidBodySystem *system,\n            atg_scs::RigidBody *rotatingMass,\n            Vehicle *vehicle,\n            Engine *engine);\n        void changeGear(int newGear);\n        inline int getGear() const { return m_gear; }\n        inline void setClutchPressure(double pressure) { m_clutchPressure = pressure; }\n        inline double getClutchPressure() const { return m_clutchPressure; }\n\n    protected:\n        atg_scs::ClutchConstraint m_clutchConstraint;\n        atg_scs::RigidBody *m_rotatingMass;\n        Vehicle *m_vehicle;\n\n        int m_gear;\n        int m_newGear;\n        int m_gearCount;\n        double *m_gearRatios;\n        double m_maxClutchTorque;\n        double m_clutchPressure;\n};\n\n#endif /* ATG_ENGINE_SIM_TRANSMISSION_H */\n"
  },
  {
    "path": "include/ui_button.h",
    "content": "#ifndef ATG_ENGINE_SIM_UI_BUTTON_H\n#define ATG_ENGINE_SIM_UI_BUTTON_H\n\n#include \"ui_element.h\"\n\nclass UiButton : public UiElement {\n    public:\n        UiButton();\n        virtual ~UiButton();\n\n        virtual void update(float dt);\n        virtual void render();\n\n        std::string m_text;\n        float m_fontSize;\n};\n\n#endif /* ATG_ENGINE_SIM_UI_BUTTON_H */\n"
  },
  {
    "path": "include/ui_element.h",
    "content": "#ifndef ATG_ENGINE_SIM_UI_ELEMENT_H\n#define ATG_ENGINE_SIM_UI_ELEMENT_H\n\n#include \"ui_math.h\"\n\n#include \"delta.h\"\n\n#include <vector>\n\nclass EngineSimApplication;\nclass UiElement {\n    public:\n        enum class Event {\n            Clicked\n        };\n\n    public:\n        UiElement();\n        virtual ~UiElement();\n\n        virtual void initialize(EngineSimApplication *app);\n        virtual void destroy();\n\n        virtual void update(float dt);\n        virtual void render();\n\n        virtual void signal(UiElement *element, Event event);\n        virtual void onMouseDown(const Point &mouseLocal);\n        virtual void onMouseUp(const Point &mouseLocal);\n        virtual void onMouseClick(const Point &mouseLocal);\n        virtual void onDrag(const Point &p0, const Point &mouse0, const Point &mouse);\n        virtual void onMouseOver(const Point &mouseLocal);\n        virtual void onMouseLeave();\n        virtual void onMouseScroll(int mouseScroll);\n\n        bool isMouseOver() const { return m_mouseOver; }\n        bool isMouseHeld() const { return m_mouseHeld; }\n\n        template <typename T_Element>\n        T_Element *addElement(UiElement *signalTarget = nullptr) {\n            T_Element *newElement = new T_Element;\n            newElement->initialize(m_app);\n            newElement->m_parent = this;\n            newElement->m_signalTarget = signalTarget;\n            newElement->m_index = (int)m_children.size();\n            m_children.push_back(newElement);\n\n            return newElement;\n        }\n\n        UiElement *mouseOver(const Point &mouseLocal);\n\n        Point getWorldPosition() const;\n\n        Point getLocalPosition() const { return m_localPosition; }\n        void setLocalPosition(const Point &p) { m_localPosition = p; }\n        void setLocalPosition(const Point &p, const Point &ref);\n\n        Point worldToLocal(const Point &wp) const { return wp - getWorldPosition(); }\n        Point localToWorld(const Point &lp) const { return lp + getWorldPosition(); }\n\n        void setVisible(bool visible) { m_visible = visible; }\n        bool isVisible() const { return m_visible; }\n\n        size_t getChildCount() const { return m_children.size(); }\n\n        void bringToFront(UiElement *element);\n        void activate();\n\n        Bounds m_bounds;\n\n    protected:\n        void signal(Event event);\n\n        float pixelsToUnits(float length) const;\n        Point pixelsToUnits(const Point &p) const;\n        float unitsToPixels(float x) const;\n        Point unitsToPixels(const Point &p) const;\n        Point getRenderPoint(const Point &p) const;\n        Bounds getRenderBounds(const Bounds &b) const;\n        Bounds unitsToPixels(const Bounds &b) const;\n\n        void resetShader();\n\n        void drawModel(\n                dbasic::ModelAsset *model,\n                const ysVector &color,\n                const Point &p,\n                const Point &scale = { 1.0f, 1.0f });\n        void drawFrame(\n                const Bounds &bounds,\n                float thickness,\n                const ysVector &frameColor,\n                const ysVector &fillColor,\n                bool fill = true);\n        void drawBox(\n                const Bounds &bounds,\n                const ysVector &fillColor);\n        void drawText(\n                const std::string &s,\n                const Bounds &bounds,\n                float height,\n                const Point &ref = Bounds::tl);\n        void drawAlignedText(\n                const std::string &s,\n                const Bounds &bounds,\n                float height,\n                const Point &ref = Bounds::tl,\n                const Point &refText = Bounds::tl);\n        void drawCenteredText(\n                const std::string &s,\n                const Bounds &bounds,\n                float height,\n                const Point &ref = Bounds::tm);\n\n    protected:\n        std::vector<UiElement *> m_children;\n        UiElement *m_parent;\n        UiElement *m_signalTarget;\n\n    protected:\n        Point m_localPosition;\n        Bounds m_mouseBounds;\n        bool m_checkMouse;\n        bool m_disabled;\n        int m_index;\n\n        bool m_draggable;\n        bool m_mouseOver;\n        bool m_mouseHeld;\n        bool m_visible;\n\n    protected:\n        EngineSimApplication *m_app;\n};\n\n#endif /* ATG_ENGINE_SIM_UI_ELEMENT_H */\n"
  },
  {
    "path": "include/ui_manager.h",
    "content": "#ifndef ATG_ENGINE_SIM_UI_MANAGER_H\n#define ATG_ENGINE_SIM_UI_MANAGER_H\n\n#include \"ui_element.h\"\n\n#include <vector>\n\nclass EngineSimApplication;\nclass UiManager {\n    public:\n        UiManager();\n        ~UiManager();\n\n        void initialize(EngineSimApplication *app);\n        void destroy();\n\n        void update(float dt);\n        void render();\n\n        UiElement *getRoot() { return &m_root; }\n\n    protected:\n        UiElement m_root;\n\n        UiElement *m_dragStart;\n        UiElement *m_hover;\n        Point m_mouse_p0;\n        Point m_drag_p0;\n\n        int m_lastMouseScroll;\n\n    protected:\n        EngineSimApplication *m_app;\n};\n\n#endif /* ATG_ENGINE_SIM_UI_MANAGER_H */\n"
  },
  {
    "path": "include/ui_math.h",
    "content": "#ifndef ATG_ENGINE_SIM_UI_MATH_H\n#define ATG_ENGINE_SIM_UI_MATH_H\n\n#include <cmath>\n\nstruct Point {\n    float x, y;\n\n    Point(float x, float y) : x(x), y(y) { /* void */ }\n    Point(float s) : x(s), y(s) { /* void */ }\n    Point() : x(0), y(0) { /* void */ }\n\n    Point operator+(const Point &b) const {\n        return { x + b.x, y + b.y };\n    }\n\n    Point operator-(const Point &b) const {\n        return { x - b.x, y - b.y };\n    }\n\n    Point operator-() const {\n        return { -x, -y };\n    }\n\n    Point operator*(const Point &b) const {\n        return { x * b.x, y * b.y };\n    }\n\n    Point operator*(float s) const {\n        return { x * s, y * s };\n    }\n\n    Point operator/(const Point &b) const {\n        return { x / b.x, y / b.y };\n    }\n\n    Point operator/(float s) const {\n        return { x / s, y / s };\n    }\n\n    Point operator+=(const Point &b) {\n        x += b.x;\n        y += b.y;\n        return { x, y };\n    }\n\n    Point operator-=(const Point &b) {\n        x -= b.x;\n        y -= b.y;\n        return { x, y };\n    }\n\n    Point operator/=(const Point &b) {\n        x /= b.x;\n        y /= b.y;\n        return { x, y };\n    }\n\n    Point operator*=(const Point &b) {\n        x *= b.x;\n        y *= b.y;\n        return { x, y };\n    }\n\n    bool operator>(const Point &b) const {\n        return x > b.x && y > b.y;\n    }\n\n    bool operator>=(const Point &b) const {\n        return x >= b.x && y >= b.y;\n    }\n\n    bool operator<(const Point &b) const {\n        return x < b.x && y < b.y;\n    }\n\n    bool operator<=(const Point &b) const {\n        return x <= b.x && y <= b.y;\n    }\n\n    float length() const {\n        return std::sqrt(x * x + y * y);\n    }\n\n    float lengthSquared() const {\n        return x * x + y * y;\n    }\n\n    float dot(const Point &b) const {\n        return x * b.x + y * b.y;\n    }\n\n    Point normalized() const {\n        return (*this) / length();\n    }\n\n    Point componentMax(const Point &b) const {\n        return { std::fmax(x, b.x), std::fmax(y, b.y) };\n    }\n\n    Point componentMin(const Point &b) const {\n        return { std::fmin(x, b.x), std::fmin(y, b.y) };\n    }\n};\n\nstruct Bounds {\n    static const Point center;\n    static const Point tl, tr, tm;\n    static const Point bl, br, bm;\n    static const Point lm, rm;\n\n    Point m0;\n    Point m1;\n\n    Bounds() { /* void */ }\n    Bounds(const Point &a, const Point &b) : m0(a), m1(b) { /* void */ }\n    Bounds(float x0, float x1, float y0, float y1) {\n        m0 = { std::fmin(x0, x1), std::fmin(y0, y1) };\n        m1 = { std::fmax(x0, x1), std::fmax(y0, y1) };\n    }\n    Bounds(float width, float height, const Point &pos, const Point &ref = tl) {\n        m0 = Point(0, 0);\n        m1 = Point(width, height);\n        setPosition(pos, ref);\n    }\n\n    bool overlaps(const Point &p) const {\n        return p >= m0 && p <= m1;\n    }\n\n    Bounds add(const Bounds &b) const {\n        return { m0.componentMin(b.m0), m1.componentMax(b.m1) };\n    }\n\n    Bounds move(const Point &delta) {\n        m0 += delta;\n        m1 += delta;\n\n        return *this;\n    }\n\n    void setPosition(const Point &pos, const Point &ref = tl) {\n        move(pos - getPosition(ref));\n    }\n\n    Point getPosition(const Point &ref = tl) const {\n        const Point offset = ref * dim();\n        return m0 + offset;\n    }\n\n    float left() const { return m0.x; }\n    float right() const { return m1.x; }\n    float top() const { return m1.y; }\n    float bottom() const { return m0.y; }\n    float center_h() const { return (m0.x + m1.x) / 2; }\n    float center_v() const { return (m0.y + m1.y) / 2; }\n\n    float width() const { return m1.x - m0.x; }\n    float height() const { return m1.y - m0.y; }\n    Point dim() const { return Point(width(), height()); }\n\n    Bounds inset(float amount) const {\n        return { m0 + amount, m1 - amount };\n    }\n\n    Bounds grow(float amount) const {\n        return inset(-amount);\n    }\n\n    Bounds verticalSplit(float s0, float s1) const {\n        const float s_min = std::fmin(s0, s1);\n        const float s_max = std::fmax(s0, s1);\n\n        return {\n                m0 + Point(0.0f, height() * s_min),\n                m0 + Point(0.0f, height() * s_max) + Point(width(), 0.0f) };\n    }\n\n    Bounds horizontalSplit(float s0, float s1) const {\n        const float s_min = std::fmin(s0, s1);\n        const float s_max = std::fmax(s0, s1);\n\n        return {\n                m0 + Point(width() * s_min, 0.0f),\n                m0 + Point(width() * s_max, 0.0f) + Point(0.0f, height()) };\n    }\n};\n\nstruct Grid {\n    int h_cells;\n    int v_cells;\n\n    Bounds get(const Bounds &a, int x, int y, int w = 1, int h = 1) const {\n        const float cellWidth = a.width() / h_cells;\n        const float cellHeight = a.height() / v_cells;\n\n        const float width = cellWidth * w;\n        const float height = cellHeight * h;\n\n        const Point p0 = a.getPosition(Bounds::tl) + Point(x * cellWidth, -y * cellHeight);\n        return Bounds(width, height, p0, Bounds::tl);\n    }\n};\n\n#endif /* ATG_ENGINE_SIM_UI_MATH_H */\n"
  },
  {
    "path": "include/ui_utilities.h",
    "content": "#ifndef ATG_ENGINE_SIM_UI_UTILITIES_H\n#define ATG_ENGINE_SIM_UI_UTILITIES_H\n\n#include \"delta.h\"\n\nysVector mix(const ysVector &c1, const ysVector &c2, float s);\n\n#endif /* ATG_ENGINE_SIM_UI_UTILITIES_H */\n"
  },
  {
    "path": "include/units.h",
    "content": "#ifndef ATG_ENGINE_SIM_UNITS_H\n#define ATG_ENGINE_SIM_UNITS_H\n\n#include \"constants.h\"\n\nnamespace units {\n    // Force\n    extern constexpr double N = 1.0;\n\n    extern constexpr double lbf = N * 4.44822;\n\n    // Mass\n    extern constexpr double kg = 1.0;\n    extern constexpr double g = kg / 1000.0;\n\n    extern constexpr double lb = 0.45359237 * kg;\n\n    // Distance\n    extern constexpr double m = 1.0;\n    extern constexpr double cm = m / 100.0;\n    extern constexpr double mm = m / 1000.0;\n    extern constexpr double km = m * 1000.0;\n\n    extern constexpr double inch = cm * 2.54;\n    extern constexpr double foot = inch * 12.0;\n    extern constexpr double thou = inch / 1000.0;\n    extern constexpr double mile = m * 1609.344;\n\n    // Time\n    extern constexpr double sec = 1.0;\n    extern constexpr double minute = 60 * sec;\n    extern constexpr double hour = 60 * minute;\n\n    // Torque\n    extern constexpr double Nm = N * m;\n    extern constexpr double ft_lb = foot * lbf;\n\n    // Power\n    extern constexpr double W = Nm / sec;\n    extern constexpr double kW = W * 1000.0;\n    extern constexpr double hp = 745.699872 * W;\n\n    // Volume\n    extern constexpr double m3 = 1.0;\n    extern constexpr double cc = cm * cm * cm;\n    extern constexpr double mL = cc;\n    extern constexpr double L = mL * 1000.0;\n    extern constexpr double cubic_feet = foot * foot * foot;\n    extern constexpr double cubic_inches = inch * inch * inch;\n    extern constexpr double gal = 3.785411784 * L;\n\n    // Molecular\n    extern constexpr double mol = 1.0;\n    extern constexpr double kmol = mol / 1000.0;\n    extern constexpr double mmol = mol / 1000000.0;\n    extern constexpr double lbmol = mol * 453.59237;\n\n    // Flow-rate (moles)\n    extern constexpr double mol_per_sec = mol / sec;\n    extern constexpr double scfm = 0.002641 * lbmol / minute;\n\n    // Area\n    extern constexpr double m2 = 1.0;\n    extern constexpr double cm2 = cm * cm;\n\n    // Pressure\n    extern constexpr double Pa = 1.0;\n    extern constexpr double kPa = Pa * 1000.0;\n    extern constexpr double MPa = Pa * 1000000.0;\n    extern constexpr double atm = 101.325 * kPa;\n\n    extern constexpr double mbar = Pa * 100.0;\n    extern constexpr double bar = mbar * 1000.0;\n\n    extern constexpr double psi = lbf / (inch * inch);\n    extern constexpr double psig = psi;\n    extern constexpr double inHg = Pa * 3386.3886666666713;\n    extern constexpr double inH2O = inHg * 0.0734824;\n\n    // Temperature\n    extern constexpr double K = 1.0;\n    extern constexpr double K0 = 273.15;\n    extern constexpr double C = K;\n    extern constexpr double F = (5.0 / 9.0) * K;\n    extern constexpr double F0 = -459.67;\n\n    // Energy\n    extern constexpr double J = 1.0;\n    extern constexpr double kJ = J * 1000;\n    extern constexpr double MJ = J * 1000000;\n\n    // Angles\n    extern constexpr double rad = 1.0;\n    extern constexpr double deg = rad * (constants::pi / 180);\n\n    // Conversions\n    inline constexpr double distance(double v, double unit) {\n        return v * unit;\n    }\n\n    inline constexpr double area(double v, double unit) {\n        return v * unit;\n    }\n\n    inline constexpr double torque(double v, double unit) {\n        return v * unit;\n    }\n\n    inline constexpr double rpm(double rpm) {\n        return rpm * 0.104719755;\n    }\n\n    inline constexpr double toRpm(double rad_s) {\n        return rad_s / 0.104719755;\n    }\n\n    inline constexpr double pressure(double v, double unit) {\n        return v * unit;\n    }\n\n    inline constexpr double psia(double p) {\n        return units::pressure(p, units::psig) - units::pressure(1.0, units::atm);\n    }\n\n    inline constexpr double toPsia(double p) {\n        return (p + units::pressure(1.0, units::atm)) / units::psig;\n    }\n\n    inline constexpr double mass(double v, double unit) {\n        return v * unit;\n    }\n\n    inline constexpr double force(double v, double unit) {\n        return v * unit;\n    }\n\n    inline constexpr double volume(double v, double unit) {\n        return v * unit;\n    }\n\n    inline constexpr double flow(double v, double unit) {\n        return v * unit;\n    }\n\n    inline constexpr double convert(double v, double unit0, double unit1) {\n        return v * (unit0 / unit1);\n    }\n\n    inline constexpr double convert(double v, double unit) {\n        return v / unit;\n    }\n\n    inline constexpr double celcius(double T_C) {\n        return T_C * C + K0;\n    }\n\n    inline constexpr double kelvin(double T) {\n        return T * K;\n    }\n\n    inline constexpr double fahrenheit(double T_F) {\n        return F * (T_F - F0);\n    }\n\n    inline constexpr double toAbsoluteFahrenheit(double T) {\n        return T / F;\n    }\n\n    inline constexpr double angle(double v, double unit) {\n        return v * unit;\n    }\n\n    inline constexpr double energy(double v, double unit) {\n        return v * unit;\n    }\n\n    // Physical Constants\n    constexpr double AirMolecularMass = units::mass(28.97, units::g) / units::mol;\n};\n\n#endif /* ATG_ENGINE_SIM_UNITS_H */\n"
  },
  {
    "path": "include/utilities.h",
    "content": "#ifndef ATG_ENGINE_SIM_UTILITIES_H\n#define ATG_ENGINE_SIM_UTILITIES_H\n\ndouble modularDistance(double a, double b, double mod = 1.0);\ndouble positiveMod(double x, double mod);\ndouble erfApproximation(double x);\n\ntemplate <typename t>\ninline t clamp(t x, t x0 = static_cast<t>(0.0), t x1 = static_cast<t>(1.0)) {\n    if (x <= x0) return x0;\n    else if (x >= x1) return x1;\n    else return x;\n}\n\n#endif /* ATG_ENGINE_SIM_UTILITIES_H */\n"
  },
  {
    "path": "include/valvetrain.h",
    "content": "#ifndef ATG_ENGINE_SIM_VALVETRAIN_H\n#define ATG_ENGINE_SIM_VALVETRAIN_H\n\nclass Camshaft;\nclass Valvetrain {\npublic:\n    Valvetrain();\n    virtual ~Valvetrain();\n\n    virtual double intakeValveLift(int cylinder) = 0;\n    virtual double exhaustValveLift(int cylinder) = 0;\n\n    virtual Camshaft *getActiveIntakeCamshaft() = 0;\n    virtual Camshaft *getActiveExhaustCamshaft() = 0;\n};\n\n#endif /* ATG_ENGINE_SIM_VALVETRAIN_H */\n"
  },
  {
    "path": "include/vehicle.h",
    "content": "#ifndef ATG_ENGINE_SIM_VEHICLE_H\n#define ATG_ENGINE_SIM_VEHICLE_H\n\n#include \"scs.h\"\n\nclass Vehicle {\n    public:\n        struct Parameters {\n            double mass;\n            double dragCoefficient;\n            double crossSectionArea;\n            double diffRatio;\n            double tireRadius;\n            double rollingResistance;\n        };\n\n    public:\n        Vehicle();\n        ~Vehicle();\n\n        void initialize(const Parameters &params);\n        void update(double dt);\n        void addToSystem(atg_scs::RigidBodySystem *system, atg_scs::RigidBody *rotatingMass);\n        inline double getMass() const { return m_mass; }\n        inline double getRollingResistance() const { return m_rollingResistance; }\n        inline double getDragCoefficient() const { return m_dragCoefficient; }\n        inline double getCrossSectionArea() const { return m_crossSectionArea; }\n        inline double getDiffRatio() const { return m_diffRatio; }\n        inline double getTireRadius() const { return m_tireRadius; }\n        double getSpeed() const;\n        inline double getTravelledDistance() const { return m_travelledDistance; }\n        inline void resetTravelledDistance() { m_travelledDistance = 0; }\n        double linearForceToVirtualTorque(double force) const;\n\n    protected:\n        atg_scs::RigidBody *m_rotatingMass;\n\n        double m_mass;\n        double m_dragCoefficient;\n        double m_crossSectionArea;\n        double m_diffRatio;\n        double m_tireRadius;\n        double m_travelledDistance;\n        double m_rollingResistance;\n};\n\n#endif /* ATG_ENGINE_SIM_VEHICLE_H */\n"
  },
  {
    "path": "include/vehicle_drag_constraint.h",
    "content": "#ifndef ATG_ENGINE_SIM_VEHICLE_DRAG_CONSTRAINT_H\n#define ATG_ENGINE_SIM_VEHICLE_DRAG_CONSTRAINT_H\n\n#include \"scs.h\"\n\nclass Vehicle;\nclass VehicleDragConstraint : public atg_scs::Constraint {\npublic:\n    VehicleDragConstraint();\n    virtual ~VehicleDragConstraint();\n\n    void initialize(atg_scs::RigidBody *rotatingMass, Vehicle *vehicle);\n\n    virtual void calculate(Output *output, atg_scs::SystemState *system);\n\n    double m_ks;\n    double m_kd;\n\nprivate:\n    Vehicle *m_vehicle;\n};\n\n#endif /* ATG_ENGINE_SIM_VEHICLE_DRAG_CONSTRAINT_H */\n"
  },
  {
    "path": "include/vtec_valvetrain.h",
    "content": "#ifndef ATG_ENGINE_SIM_VTEC_STANDARD_VALVETRAIN_H\n#define ATG_ENGINE_SIM_VTEC_STANDARD_VALVETRAIN_H\n\n#include \"valvetrain.h\"\n\nclass Engine;\nclass VtecValvetrain : public Valvetrain {\npublic:\n    struct Parameters {\n        double minRpm;\n        double minSpeed;\n        double manifoldVacuum;\n        double minThrottlePosition;\n\n        Camshaft *intakeCamshaft;\n        Camshaft *exhaustCamshaft;\n\n        Camshaft *vtecIntakeCamshaft;\n        Camshaft *vtexExhaustCamshaft;\n\n        Engine *engine;\n    };\n\npublic:\n    VtecValvetrain();\n    virtual ~VtecValvetrain();\n\n    void initialize(const Parameters &parameters);\n\n    virtual double intakeValveLift(int cylinder) override;\n    virtual double exhaustValveLift(int cylinder) override;\n\n    virtual Camshaft *getActiveIntakeCamshaft() override;\n    virtual Camshaft *getActiveExhaustCamshaft() override;\n\nprivate:\n    bool isVtecEnabled() const;\n\n    Camshaft *m_intakeCamshaft;\n    Camshaft *m_exhaustCamshaft;\n\n    Camshaft *m_vtecIntakeCamshaft;\n    Camshaft *m_vtecExhaustCamshaft;\n\n    Engine *m_engine;\n\n    double m_minRpm;\n    double m_minSpeed;\n    double m_manifoldVacuum;\n    double m_minThrottlePosition;\n};\n\n#endif /* ATG_ENGINE_SIM_VTEC_STANDARD_VALVETRAIN_H */\n"
  },
  {
    "path": "scripting/include/actions.h",
    "content": "#ifndef ATG_ENGINE_SIM_ACTIONS_H\n#define ATG_ENGINE_SIM_ACTIONS_H\n\n#include \"piranha.h\"\n\n#include \"compiler.h\"\n#include \"object_reference_node.h\"\n#include \"engine_node.h\"\n#include \"crankshaft_node.h\"\n#include \"rod_journal_node.h\"\n#include \"cylinder_bank_node.h\"\n#include \"function_node.h\"\n#include \"camshaft_node.h\"\n#include \"cylinder_head_node.h\"\n#include \"cylinder_bank_node.h\"\n#include \"ignition_module_node.h\"\n#include \"transmission_node.h\"\n#include \"vehicle_node.h\"\n\nnamespace es_script {\n\n    class SetEngineNode : public piranha::Node {\n    public:\n        SetEngineNode() { /* void */ }\n        virtual ~SetEngineNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            registerInput(&m_engineInput, \"engine\");\n        }\n\n        virtual void _evaluate() {\n            EngineNode *engineNode = getObject<EngineNode>(m_engineInput);\n\n            Engine *engine = new Engine;\n            engineNode->buildEngine(engine);\n            Compiler::output()->engine = engine;\n        }\n\n    protected:\n        piranha::pNodeInput m_engineInput = nullptr;\n    };\n\n    class AddRodJournalNode : public Node {\n    public:\n        AddRodJournalNode() { /* void */ }\n        virtual ~AddRodJournalNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"crankshaft\", &m_crankshaft, InputTarget::Type::Object);\n            addInput(\"rod_journal\", &m_rodJournal, InputTarget::Type::Object);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            m_crankshaft->addRodJournal(m_rodJournal);\n        }\n\n    protected:\n        CrankshaftNode *m_crankshaft = nullptr;\n        RodJournalNode *m_rodJournal = nullptr;\n    };\n\n    class AddSlaveJournalNode : public Node {\n    public:\n        AddSlaveJournalNode() { /* void */ }\n        virtual ~AddSlaveJournalNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"rod\", &m_rod, InputTarget::Type::Object);\n            addInput(\"rod_journal\", &m_rodJournal, InputTarget::Type::Object);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            m_rod->addRodJournal(m_rodJournal);\n        }\n\n    protected:\n        ConnectingRodNode *m_rod = nullptr;\n        RodJournalNode *m_rodJournal = nullptr;\n    };\n\n    class AddCrankshaftNode : public Node {\n    public:\n        AddCrankshaftNode() { /* void */ }\n        virtual ~AddCrankshaftNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"engine\", &m_engine, InputTarget::Type::Object);\n            addInput(\"crankshaft\", &m_crankshaft, InputTarget::Type::Object);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            m_engine->addCrankshaft(m_crankshaft);\n        }\n\n    protected:\n        CrankshaftNode *m_crankshaft = nullptr;\n        EngineNode *m_engine = nullptr;\n    };\n\n    class AddCylinderBankNode : public Node {\n    public:\n        AddCylinderBankNode() { /* void */ }\n        virtual ~AddCylinderBankNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"engine\", &m_engine, InputTarget::Type::Object);\n            addInput(\"cylinder_bank\", &m_cylinderBankNode, InputTarget::Type::Object);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            m_engine->addCylinderBank(m_cylinderBankNode);\n        }\n\n    protected:\n        CylinderBankNode *m_cylinderBankNode = nullptr;\n        EngineNode *m_engine = nullptr;\n    };\n\n    class AddCylinderNode : public Node {\n    public:\n        AddCylinderNode() { /* void */ }\n        virtual ~AddCylinderNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"piston\", &m_pistonNode, InputTarget::Type::Object);\n            addInput(\"connecting_rod\", &m_connectingRod, InputTarget::Type::Object);\n            addInput(\"rod_journal\", &m_rodJournal, InputTarget::Type::Object);\n            addInput(\"exhaust_system\", &m_exhaustSystem, InputTarget::Type::Object);\n            addInput(\"intake\", &m_intake, InputTarget::Type::Object);\n            addInput(\"cylinder_bank\", &m_cylinderBank, InputTarget::Type::Object);\n            addInput(\"ignition_wire\", &m_ignitionWire, InputTarget::Type::Object);\n            addInput(\"primary_length\", &m_primaryLength);\n            addInput(\"sound_attenuation\", &m_soundAttenuation);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            m_cylinderBank->addCylinder(\n                m_pistonNode,\n                m_connectingRod,\n                m_rodJournal,\n                m_intake,\n                m_exhaustSystem,\n                m_ignitionWire,\n                m_soundAttenuation,\n                m_primaryLength\n            );\n        }\n\n    protected:\n        PistonNode *m_pistonNode = nullptr;\n        ConnectingRodNode *m_connectingRod = nullptr;\n        RodJournalNode *m_rodJournal = nullptr;\n        CylinderBankNode *m_cylinderBank = nullptr;\n        ExhaustSystemNode *m_exhaustSystem = nullptr;\n        IntakeNode *m_intake = nullptr;\n        IgnitionWireNode *m_ignitionWire = nullptr;\n        double m_primaryLength = 0.0;\n        double m_soundAttenuation = 1.0;\n    };\n\n    class AddSampleNode : public Node {\n    public:\n        AddSampleNode() { /* void */ }\n        virtual ~AddSampleNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"x\", &m_x);\n            addInput(\"y\", &m_y);\n            addInput(\"function\", &m_function, InputTarget::Type::Object);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            m_function->addSample(m_x, m_y);\n        }\n\n    protected:\n        double m_x = 0;\n        double m_y = 0;\n        FunctionNode *m_function = nullptr;\n    };\n\n    class AddLobeNode : public Node {\n    public:\n        AddLobeNode() { /* void */ }\n        virtual ~AddLobeNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"centerline\", &m_centerline);\n            addInput(\"camshaft\", &m_camshaft, InputTarget::Type::Object);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            m_camshaft->addLobe(m_centerline);\n        }\n\n    protected:\n        double m_centerline = 0;\n        CamshaftNode *m_camshaft = nullptr;\n    };\n\n    class SetCylinderHeadNode : public Node {\n    public:\n        SetCylinderHeadNode() { /* void */ }\n        virtual ~SetCylinderHeadNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"head\", &m_head, InputTarget::Type::Object);\n            addInput(\"bank\", &m_bank, InputTarget::Type::Object);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            if (m_bank->getCylinderHead() != nullptr) {\n                throwError(\"Cylinder bank already has a head\");\n                return;\n            }\n            else if (m_head->getBank() != nullptr) {\n                throwError(\"Cylinder head already attached to a bank\");\n                return;\n            }\n\n            m_bank->setCylinderHead(m_head);\n            m_head->setBank(m_bank);\n        }\n\n    protected:\n        CylinderHeadNode *m_head = nullptr;\n        CylinderBankNode *m_bank = nullptr;\n    };\n\n    class k_28inH2ONode : public Node {\n        class k_28inH2ONodeOutput : public piranha::NodeOutput {\n        public:\n            k_28inH2ONodeOutput() : NodeOutput(&piranha::FundamentalType::FloatType) {\n                m_input = 0.0;\n            }\n\n            virtual ~k_28inH2ONodeOutput() {\n                /* void */\n            }\n\n            virtual void fullCompute(void *target) const {\n                *reinterpret_cast<double *>(target) = GasSystem::k_28inH2O(m_input);\n            }\n\n            double getInput() const { return m_input; }\n            void setInput(double data) { m_input = data; }\n\n        protected:\n            double m_input;\n        };\n\n    public:\n        k_28inH2ONode() { /* void */ }\n        virtual ~k_28inH2ONode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"flow\", &m_flowInput);\n\n            Node::registerInputs();\n        }\n\n        virtual void registerOutputs() {\n            registerOutput(&m_output, \"__out\");\n\n            setPrimaryOutput(\"__out\");\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            m_output.setInput(m_flowInput);\n        }\n\n    protected:\n        k_28inH2ONodeOutput m_output;\n        double m_flowInput = 0.0;\n    };\n\n    class k_CarbNode : public Node {\n        class k_CarbNodeOutput : public piranha::NodeOutput {\n        public:\n            k_CarbNodeOutput() : NodeOutput(&piranha::FundamentalType::FloatType) {\n                m_input = 0.0;\n            }\n\n            virtual ~k_CarbNodeOutput() {\n                /* void */\n            }\n\n            virtual void fullCompute(void *target) const {\n                *reinterpret_cast<double *>(target) = GasSystem::k_carb(m_input);\n            }\n\n            double getInput() const { return m_input; }\n            void setInput(double data) { m_input = data; }\n\n        protected:\n            double m_input;\n        };\n\n    public:\n        k_CarbNode() { /* void */ }\n        virtual ~k_CarbNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"flow\", &m_flowInput);\n\n            Node::registerInputs();\n        }\n\n        virtual void registerOutputs() {\n            registerOutput(&m_output, \"__out\");\n\n            setPrimaryOutput(\"__out\");\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            m_output.setInput(m_flowInput);\n        }\n\n    protected:\n        k_CarbNodeOutput m_output;\n        double m_flowInput = 0.0;\n    };\n\n    class ConnectIgnitionWireNode : public Node {\n    public:\n        ConnectIgnitionWireNode() { /* void */ }\n        virtual ~ConnectIgnitionWireNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"wire\", &m_wire, InputTarget::Type::Object);\n            addInput(\"ignition_module\", &m_module, InputTarget::Type::Object);\n            addInput(\"angle\", &m_angle);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            m_module->connect(m_wire, m_angle);\n        }\n\n    protected:\n        IgnitionWireNode *m_wire = nullptr;\n        IgnitionModuleNode *m_module = nullptr;\n        double m_angle = 0.0;\n    };\n\n    class AddIgnitionModuleNode : public Node {\n    public:\n        AddIgnitionModuleNode() { /* void */ }\n        virtual ~AddIgnitionModuleNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"engine\", &m_engine, InputTarget::Type::Object);\n            addInput(\"ignition_module\", &m_ignitionModule, InputTarget::Type::Object);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            m_engine->addIgnitionModule(m_ignitionModule);\n        }\n\n    protected:\n        IgnitionModuleNode *m_ignitionModule = nullptr;\n        EngineNode *m_engine = nullptr;\n    };\n\n    class GenerateHarmonicCamLobeNode : public Node {\n    public:\n        GenerateHarmonicCamLobeNode() { /* void */ }\n        virtual ~GenerateHarmonicCamLobeNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"duration_at_50_thou\", &m_durationAt50Thou);\n            addInput(\"gamma\", &m_gamma);\n            addInput(\"lift\", &m_lift);\n            addInput(\"steps\", &m_steps);\n            addInput(\"function\", &m_function, InputTarget::Type::Object);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            const double angle = m_durationAt50Thou / 4;\n            const double s = std::pow(2 * units::distance(50, units::thou) / m_lift, 1 / m_gamma) - 1;\n            const double k = std::acos(s) / angle;\n            const double extents = constants::pi / k;\n\n            // pi / 2 = k * x\n\n            const double step = extents / (m_steps - 5.0);\n            for (int i = 0; i < m_steps; ++i) {\n                if (i == 0) {\n                    m_function->addSample(0.0, m_lift);\n                }\n                else {\n                    const double x = i * step;\n                    const double lift = (x >= extents)\n                        ? 0.0\n                        : m_lift * std::pow(0.5 + 0.5 * std::cos(k * x), m_gamma);\n                    m_function->addSample(x, lift);\n                    m_function->addSample(-x, lift);\n                }\n            }\n\n            m_function->setFilterRadius(step);\n        }\n\n    protected:\n        double m_durationAt50Thou = 0.0;\n        double m_gamma = 1.0;\n        double m_lift = 300.0;\n        int m_steps = 100;\n        FunctionNode *m_function = nullptr;\n    };\n\n    class AddGearNode : public Node {\n    public:\n        AddGearNode() { /* void */ }\n        virtual ~AddGearNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"ratio\", &m_ratio, InputTarget::Type::Object);\n            addInput(\"transmission\", &m_transmission, InputTarget::Type::Object);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            m_transmission->addGear(m_ratio);\n        }\n\n    protected:\n        double m_ratio = 1.0;\n        TransmissionNode *m_transmission = nullptr;\n    };\n\n    class SetTransmissionNode : public Node {\n    public:\n        SetTransmissionNode() { /* void */ }\n        virtual ~SetTransmissionNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() override {\n            addInput(\"transmission\", &m_transmission, InputTarget::Type::Object);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            Transmission *transmission = new Transmission;\n            m_transmission->generate(transmission);\n            Compiler::output()->transmission = transmission;\n        }\n\n    protected:\n        TransmissionNode *m_transmission = nullptr;\n    };\n\n    class SetVehicleNode : public piranha::Node {\n    public:\n        SetVehicleNode() { /* void */ }\n        virtual ~SetVehicleNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            registerInput(&m_vehicle, \"vehicle\");\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            VehicleNode *vehicleNode = getObject<VehicleNode>(m_vehicle);\n\n            Vehicle *vehicle = new Vehicle;\n            vehicleNode->generate(vehicle);\n            Compiler::output()->vehicle = vehicle;\n        }\n\n    protected:\n        piranha::pNodeInput m_vehicle = nullptr;\n    };\n\n    class SetApplicationSettingsNode : public Node {\n    public:\n        SetApplicationSettingsNode() { /* void */ }\n        virtual ~SetApplicationSettingsNode() { /* void */ }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"start_fullscreen\", &m_settings.startFullscreen);\n            addInput(\"power_units\", &m_settings.powerUnits);\n            addInput(\"torque_units\", &m_settings.torqueUnits);\n            addInput(\"speed_units\", &m_settings.speedUnits);\n            addInput(\"pressure_units\", &m_settings.pressureUnits);\n            addInput(\"boost_units\", &m_settings.boostUnits);\n\n            addInput(\"color_background\", &m_settings.colorBackground);\n            addInput(\"color_foreground\", &m_settings.colorForeground);\n            addInput(\"color_shadow\", &m_settings.colorShadow);\n            addInput(\"color_highlight1\", &m_settings.colorHighlight1);\n            addInput(\"color_highlight2\", &m_settings.colorHighlight2);\n            addInput(\"color_pink\", &m_settings.colorPink);\n            addInput(\"color_red\", &m_settings.colorRed);\n            addInput(\"color_orange\", &m_settings.colorOrange);\n            addInput(\"color_yellow\", &m_settings.colorYellow);\n            addInput(\"color_blue\", &m_settings.colorBlue);\n            addInput(\"color_green\", &m_settings.colorGreen);\n\n            Node::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            readAllInputs();\n\n            Compiler::output()->applicationSettings = m_settings;\n        }\n\n    protected:\n        ApplicationSettings m_settings;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_ACTIONS_H */\n"
  },
  {
    "path": "scripting/include/camshaft_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_CAMSHAFT_NODE_H\n#define ATG_ENGINE_SIM_CAMSHAFT_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"engine_context.h\"\n#include \"function_node.h\"\n\n#include \"engine_sim.h\"\n\n#include <map>\n#include <vector>\n\nnamespace es_script {\n\n    class CamshaftNode : public ObjectReferenceNode<CamshaftNode> {\n    public:\n        CamshaftNode() { /* void */ }\n        virtual ~CamshaftNode() { /* void */ }\n\n        void generate(\n            Camshaft *camshaft,\n            Crankshaft *crankshaft,\n            EngineContext *context) const\n        {\n            Camshaft::Parameters parameters = m_parameters;\n            parameters.crankshaft = crankshaft;\n            parameters.lobes = (int)m_lobes.size();\n            parameters.lobeProfile = m_lobeProfile->generate(context);\n            \n            camshaft->initialize(parameters);\n\n            for (int i = 0; i < parameters.lobes; ++i) {\n                camshaft->setLobeCenterline(i, m_lobes[i]);\n            }\n        }\n\n        void addLobe(double lobeCenterline) {\n            m_lobes.push_back(lobeCenterline);\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"advance\", &m_parameters.advance);\n            addInput(\"base_radius\", &m_parameters.baseRadius);\n            addInput(\"lobe_profile\", &m_lobeProfile);\n\n            ObjectReferenceNode<CamshaftNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n\n            m_parameters.crankshaft = nullptr;\n        }\n\n        Camshaft::Parameters m_parameters;\n        FunctionNode *m_lobeProfile = nullptr;\n        std::vector<double> m_lobes;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_CAMSHAFT_NODE_H */\n"
  },
  {
    "path": "scripting/include/channel_types.h",
    "content": "#ifndef ATG_ENGINE_SIM_CHANNEL_TYPES_H\n#define ATG_ENGINE_SIM_CHANNEL_TYPES_H\n\n#include \"piranha.h\"\n\nnamespace es_script {\n\n    struct ObjectChannel {\n        static const piranha::ChannelType EngineChannel;\n        static const piranha::ChannelType CrankshaftChannel;\n        static const piranha::ChannelType RodJournalChannel;\n        static const piranha::ChannelType ConnectingRodChannel;\n        static const piranha::ChannelType CylinderBankChannel;\n        static const piranha::ChannelType PistonChannel;\n        static const piranha::ChannelType FunctionChannel;\n        static const piranha::ChannelType IntakeChannel;\n        static const piranha::ChannelType ExhaustSystemChannel;\n        static const piranha::ChannelType CylinderHeadChannel;\n        static const piranha::ChannelType CamshaftChannel;\n        static const piranha::ChannelType IgnitionModuleChannel;\n        static const piranha::ChannelType IgnitionWireChannel;\n        static const piranha::ChannelType FuelChannel;\n        static const piranha::ChannelType ImpulseResponseChannel;\n        static const piranha::ChannelType ValvetrainChannel;\n        static const piranha::ChannelType VehicleChannel;\n        static const piranha::ChannelType TransmissionChannel;\n        static const piranha::ChannelType ThrottleChannel;\n    };\n\n    template <typename Type>\n    extern inline const piranha::ChannelType *LookupChannelType() {\n        static_assert(false, \"Invalid type lookup\");\n        return nullptr;\n    }\n\n#define ASSIGN_CHANNEL_TYPE(type, channel) \\\n    class type; \\\n    template <> extern inline const piranha::ChannelType *LookupChannelType<type>() { \\\n        return &ObjectChannel::channel; \\\n    }\n\n    // Register all types\n    ASSIGN_CHANNEL_TYPE(EngineNode, EngineChannel);\n    ASSIGN_CHANNEL_TYPE(CrankshaftNode, CrankshaftChannel);\n    ASSIGN_CHANNEL_TYPE(RodJournalNode, RodJournalChannel);\n    ASSIGN_CHANNEL_TYPE(ConnectingRodNode, ConnectingRodChannel);\n    ASSIGN_CHANNEL_TYPE(CylinderBankNode, CylinderBankChannel);\n    ASSIGN_CHANNEL_TYPE(PistonNode, PistonChannel);\n    ASSIGN_CHANNEL_TYPE(FunctionNode, FunctionChannel);\n    ASSIGN_CHANNEL_TYPE(IntakeNode, IntakeChannel);\n    ASSIGN_CHANNEL_TYPE(ExhaustSystemNode, ExhaustSystemChannel);\n    ASSIGN_CHANNEL_TYPE(CylinderHeadNode, CylinderHeadChannel);\n    ASSIGN_CHANNEL_TYPE(CamshaftNode, CamshaftChannel);\n    ASSIGN_CHANNEL_TYPE(IgnitionModuleNode, IgnitionModuleChannel);\n    ASSIGN_CHANNEL_TYPE(IgnitionWireNode, IgnitionWireChannel);\n    ASSIGN_CHANNEL_TYPE(FuelNode, FuelChannel);\n    ASSIGN_CHANNEL_TYPE(ImpulseResponseNode, ImpulseResponseChannel);\n    ASSIGN_CHANNEL_TYPE(ValvetrainNode, ValvetrainChannel);\n    ASSIGN_CHANNEL_TYPE(VehicleNode, VehicleChannel);\n    ASSIGN_CHANNEL_TYPE(TransmissionNode, VehicleChannel);\n    ASSIGN_CHANNEL_TYPE(ThrottleNode, ThrottleChannel);\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_CHANNEL_TYPES_H */\n"
  },
  {
    "path": "scripting/include/compiler.h",
    "content": "#ifndef ATG_ENGINE_SIM_COMPILER_H\n#define ATG_ENGINE_SIM_COMPILER_H\n\n#include \"language_rules.h\"\n\n#include \"engine_sim.h\"\n#include \"piranha.h\"\n\n#include <vector>\n\nnamespace es_script {\n\n    class Compiler {\n    public:\n        struct Output {\n            Engine *engine = nullptr;\n            Vehicle *vehicle = nullptr;\n            Transmission *transmission = nullptr;\n            Simulator::Parameters simulatorParameters;\n            ApplicationSettings applicationSettings;\n\n            std::vector<Function *> functions;\n        };\n\n    private:\n        static Output *s_output;\n\n    public:\n        Compiler();\n        ~Compiler();\n\n        static Output *output();\n\n        void initialize();\n        bool compile(const piranha::IrPath &path);\n        Output execute();\n        void destroy();\n\n    private:\n        void printError(const piranha::CompilationError *err, std::ofstream &file) const;\n\n    private:\n        LanguageRules m_rules;\n        piranha::Compiler *m_compiler;\n        piranha::NodeProgram m_program;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_COMPILER_H */"
  },
  {
    "path": "scripting/include/connecting_rod_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_CONNECTING_ROD_NODE_H\n#define ATG_ENGINE_SIM_CONNECTING_ROD_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"rod_journal_node.h\"\n\n#include \"engine_sim.h\"\n\n#include <map>\n#include <vector>\n\nnamespace es_script {\n\n    class ConnectingRodNode : public ObjectReferenceNode<ConnectingRodNode> {\n    public:\n        ConnectingRodNode() { /* void */ }\n        virtual ~ConnectingRodNode() { /* void */ }\n\n        void addRodJournal(RodJournalNode *rodJournal) {\n            m_rodJournals.push_back(rodJournal);\n            rodJournal->m_rod = this;\n        }\n\n        bool isMaster() const {\n            return !m_rodJournals.empty();\n        }\n\n        void indexSlaveJournals(EngineContext *context) const {\n            int index = 0;\n            for (RodJournalNode *journal : m_rodJournals) {\n                context->addRodJournal(journal, index++);\n            }\n        }\n\n        void generate(\n            ConnectingRod *connectingRod,\n            Crankshaft *crankshaft,\n            Piston *piston,\n            int rodJournal) const\n        {\n            ConnectingRod::Parameters params = m_parameters;\n            params.crankshaft = crankshaft;\n            params.journal = rodJournal;\n            params.piston = piston;\n            params.rodJournals = static_cast<int>(m_rodJournals.size());\n            params.master = nullptr;\n\n            connectingRod->initialize(params);\n\n            for (int i = 0; i < params.rodJournals; ++i) {\n                connectingRod->setRodJournalAngle(i, m_rodJournals[i]->getAngle() + constants::pi / 2);\n            }\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"mass\", &m_parameters.mass);\n            addInput(\"moment_of_inertia\", &m_parameters.momentOfInertia);\n            addInput(\"center_of_mass\", &m_parameters.centerOfMass);\n            addInput(\"length\", &m_parameters.length);\n            addInput(\"slave_throw\", &m_parameters.slaveThrow);\n\n            ObjectReferenceNode<ConnectingRodNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n\n            m_parameters.crankshaft = nullptr;\n            m_parameters.piston = nullptr;\n        }\n\n        ConnectingRod::Parameters m_parameters;\n        std::vector<RodJournalNode *> m_rodJournals;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_CONNECTING_ROD_NODE_H */\n"
  },
  {
    "path": "scripting/include/crankshaft_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_CRANKSHAFT_NODE_H\n#define ATG_ENGINE_SIM_CRANKSHAFT_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"rod_journal_node.h\"\n#include \"engine_context.h\"\n\n#include \"engine_sim.h\"\n\n#include <map>\n#include <vector>\n\nnamespace es_script {\n\n    class CrankshaftNode : public ObjectReferenceNode<CrankshaftNode> {\n    public:\n        CrankshaftNode() { /* void */ }\n        virtual ~CrankshaftNode() { /* void */ }\n\n        void addRodJournal(RodJournalNode *rodJournal) {\n            m_rodJournals.push_back(rodJournal);\n            rodJournal->m_crankshaft = this;\n        }\n\n        void generate(Crankshaft *crankshaft, EngineContext *context) {\n            Crankshaft::Parameters params = m_parameters;\n            params.rodJournals = (int)m_rodJournals.size();\n\n            crankshaft->initialize(params);\n            for (int i = 0; i < params.rodJournals; ++i) {\n                RodJournalNode *rodJournal = m_rodJournals[i];\n                crankshaft->setRodJournalAngle(\n                    i,\n                    rodJournal->getAngle()\n                );\n                context->addRodJournal(rodJournal, i);\n            }\n\n            context->addCrankshaft(this, crankshaft);\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"throw\", &m_parameters.crankThrow);\n            addInput(\"flywheel_mass\", &m_parameters.flywheelMass);\n            addInput(\"mass\", &m_parameters.mass);\n            addInput(\"friction_torque\", &m_parameters.frictionTorque);\n            addInput(\"moment_of_inertia\", &m_parameters.momentOfInertia);\n            addInput(\"position_x\", &m_parameters.pos_x);\n            addInput(\"position_y\", &m_parameters.pos_y);\n            addInput(\"tdc\", &m_parameters.tdc);\n\n            ObjectReferenceNode<CrankshaftNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n\n            m_parameters.rodJournals = 0;\n        }\n\n        Crankshaft::Parameters m_parameters;\n        std::vector<RodJournalNode *> m_rodJournals;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_CRANKSHAFT_NODE_H */\n"
  },
  {
    "path": "scripting/include/cylinder_bank_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_CYLINDER_BANK_NODE_H\n#define ATG_ENGINE_SIM_CYLINDER_BANK_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"rod_journal_node.h\"\n#include \"piston_node.h\"\n#include \"connecting_rod_node.h\"\n#include \"cylinder_head_node.h\"\n#include \"ignition_wire_node.h\"\n\n#include \"engine_sim.h\"\n\n#include <map>\n#include <vector>\n\nnamespace es_script {\n\n    class CylinderBankNode : public ObjectReferenceNode<CylinderBankNode> {\n    public:\n        struct Cylinder {\n            PistonNode *piston;\n            ConnectingRodNode *rod;\n            RodJournalNode *rodJournal;\n            IntakeNode *intake;\n            ExhaustSystemNode *exhaust;\n            double soundAttenuation;\n            double primaryLength;\n        };\n\n    public:\n        CylinderBankNode() { /* void */ }\n        virtual ~CylinderBankNode() { /* void */ }\n\n        void connectRodAssemblies(EngineContext *context) const {\n            const int n = getCylinderCount();\n            for (int i = 0; i < n; ++i) {\n                ConnectingRodNode *rodNode = m_cylinders[i].rod;\n                ConnectingRod *rod = context->getConnectingRod(rodNode);\n\n                RodJournalNode *journal = m_cylinders[i].rodJournal;\n                if (journal->getRod() != nullptr) {\n                    rod->setMaster(context->getConnectingRod(journal->getRod()));\n                    rod->setCrankshaft(rod->getMasterRod()->getCrankshaft());\n                }\n            }\n        }\n\n        void indexSlaveJournals(EngineContext *context) const {\n            const int n = getCylinderCount();\n            for (int i = 0; i < n; ++i) {\n                m_cylinders[i].rod->indexSlaveJournals(context);\n            }\n        }\n\n        void generate(\n            int index,\n            int cylinderBaseIndex,\n            CylinderBank *cylinderBank,\n            Crankshaft *crankshaft,\n            Engine *engine,\n            EngineContext *context) const \n        {\n            CylinderBank::Parameters params = m_parameters;\n            params.cylinderCount = static_cast<int>(m_cylinders.size());\n            params.index = index;\n            params.crankshaft = crankshaft;\n\n            cylinderBank->initialize(params);\n\n            const int n = getCylinderCount();\n            for (int i = 0; i < n; ++i) {\n                Piston *piston = engine->getPiston(cylinderBaseIndex + i);\n                ConnectingRod *rod = engine->getConnectingRod(cylinderBaseIndex + i);\n                Crankshaft *crankshaft = context->getCrankshaft(m_cylinders[i].rodJournal->getCrankshaft());\n\n                context->addConnectingRod(m_cylinders[i].rod, rod);\n\n                context->setCylinderIndex(\n                    this,\n                    i,\n                    cylinderBaseIndex + i);\n\n                m_cylinders[i].piston->generate(\n                    piston,\n                    rod,\n                    cylinderBank,\n                    i);\n                m_cylinders[i].rod->generate(\n                    rod,\n                    crankshaft,\n                    piston,\n                    context->getRodJournalIndex(m_cylinders[i].rodJournal));\n            }\n\n            CylinderHead *head = context->getHead(m_head);\n            m_head->generate(head, cylinderBank, crankshaft, context);\n\n            const int n_cylinders = getCylinderCount();\n            for (int i = 0; i < n_cylinders; ++i) {\n                ExhaustSystemNode *exhaustNode = getCylinder(i).exhaust;\n                IntakeNode *intakeNode = getCylinder(i).intake;\n\n                ExhaustSystem *exhaust = exhaustNode->generate(context);\n                Intake *intake = intakeNode->generate(context);\n\n                head->setIntake(i, intake);\n                head->setExhaustSystem(i, exhaust);\n                head->setSoundAttenuation(i, getCylinder(i).soundAttenuation);\n                head->setHeaderPrimaryLength(i, getCylinder(i).primaryLength);\n            }\n        }\n\n        void addCylinder(\n            PistonNode *piston,\n            ConnectingRodNode *rod,\n            RodJournalNode *rodJournal,\n            IntakeNode *intake,\n            ExhaustSystemNode *exhaust,\n            IgnitionWireNode *wire,\n            double soundAttenuation,\n            double primaryLength)\n        {\n            m_cylinders.push_back({\n                piston,\n                rod,\n                rodJournal,\n                intake,\n                exhaust,\n                soundAttenuation,\n                primaryLength\n            });\n\n            wire->connect(this, getCylinderCount() - 1);\n        }\n\n        const Cylinder &getCylinder(int i) const {\n            return m_cylinders[i];\n        }\n\n        int getCylinderCount() const {\n            return (int)m_cylinders.size();\n        }\n\n        void setCylinderHead(CylinderHeadNode *head) {\n            m_head = head;\n        }\n\n        CylinderHeadNode *getCylinderHead() const {\n            return m_head;\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"angle\", &m_parameters.angle);\n            addInput(\"bore\", &m_parameters.bore);\n            addInput(\"deck_height\", &m_parameters.deckHeight);\n            addInput(\"position_x\", &m_parameters.positionX);\n            addInput(\"position_y\", &m_parameters.positionY);\n            addInput(\"display_depth\", &m_parameters.displayDepth);\n\n            ObjectReferenceNode<CylinderBankNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n\n            m_parameters.cylinderCount = 0;\n            m_parameters.index = 0;\n        }\n\n        CylinderBank::Parameters m_parameters;\n        std::vector<Cylinder> m_cylinders;\n        CylinderHeadNode *m_head = nullptr;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_CYLINDER_BANK_NODE_H */\n"
  },
  {
    "path": "scripting/include/cylinder_head_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_CYLINDER_HEAD_NODE_H\n#define ATG_ENGINE_SIM_CYLINDER_HEAD_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"valvetrain_node.h\"\n#include \"function_node.h\"\n#include \"exhaust_system_node.h\"\n#include \"intake_node.h\"\n\n#include \"engine_sim.h\"\n\n#include <map>\n#include <vector>\n\nnamespace es_script {\n\n    class CylinderHeadNode : public ObjectReferenceNode<CylinderHeadNode> {\n    public:\n        CylinderHeadNode() { /* void */ }\n        virtual ~CylinderHeadNode() { /* void */ }\n\n        void generate(\n            CylinderHead *head,\n            CylinderBank *bank,\n            Crankshaft *crankshaft,\n            EngineContext *context) const\n        {\n            CylinderHead::Parameters params = m_parameters;\n            params.Bank = bank;\n            params.Valvetrain = m_valvetrain->generate(context, crankshaft);\n            params.IntakePortFlow = m_intakePortFlow->generate(context);\n            params.ExhaustPortFlow = m_exhaustPortFlow->generate(context);\n\n            head->initialize(params);\n        }\n\n        void setBank(CylinderBankNode *bank) { m_bank = bank; }\n        CylinderBankNode *getBank() const { return m_bank; }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\n                \"intake_port_flow\",\n                &m_intakePortFlow,\n                InputTarget::Type::Object);\n            addInput(\n                \"exhaust_port_flow\",\n                &m_exhaustPortFlow,\n                InputTarget::Type::Object);\n            addInput(\n                \"valvetrain\",\n                &m_valvetrain,\n                InputTarget::Type::Object);\n            addInput(\"chamber_volume\", &m_parameters.CombustionChamberVolume);\n            addInput(\"flip_display\", &m_parameters.FlipDisplay);\n            addInput(\"intake_runner_volume\", &m_parameters.IntakeRunnerVolume);\n            addInput(\"intake_runner_cross_section_area\", &m_parameters.IntakeRunnerCrossSectionArea);\n            addInput(\"exhaust_runner_volume\", &m_parameters.ExhaustRunnerVolume);\n            addInput(\"exhaust_runner_cross_section_area\", &m_parameters.ExhaustRunnerCrossSectionArea);\n\n            ObjectReferenceNode<CylinderHeadNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n\n            m_parameters.Bank = nullptr;\n            m_parameters.Valvetrain = nullptr;\n            m_parameters.IntakePortFlow = nullptr;\n            m_parameters.ExhaustPortFlow = nullptr;\n        }\n\n        CylinderHead::Parameters m_parameters;\n\n        CylinderBankNode *m_bank = nullptr;\n        FunctionNode *m_intakePortFlow = nullptr;\n        FunctionNode *m_exhaustPortFlow = nullptr;\n        ValvetrainNode *m_valvetrain = nullptr;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_CYLINDER_HEAD_NODE_H */\n"
  },
  {
    "path": "scripting/include/engine_context.h",
    "content": "#ifndef ATG_ENGINE_SIM_ENGINE_CONTEXT_H\n#define ATG_ENGINE_SIM_ENGINE_CONTEXT_H\n\n#include \"engine_sim.h\"\n\n#include <map>\n\nnamespace es_script {\n\n    class CylinderHeadNode;\n    class CylinderBankNode;\n    class CrankshaftNode;\n    class FunctionNode;\n    class ExhaustSystemNode;\n    class IntakeNode;\n    class RodJournalNode;\n    class CamshaftNode;\n    class ImpulseResponseNode;\n    class ValvetrainNode;\n    class ConnectingRodNode;\n\n    class EngineContext {\n    public:\n        EngineContext();\n        ~EngineContext();\n\n        void addHead(CylinderHeadNode *node, CylinderHead *head);\n        CylinderHead *getHead(CylinderHeadNode *head);\n\n        void addBank(CylinderBankNode *node, CylinderBank *bank);\n        CylinderBank *getBank(CylinderBankNode *bank) const;\n\n        void addRodJournal(RodJournalNode *node, int index);\n        int getRodJournalIndex(RodJournalNode *node) const;\n\n        void addIntake(IntakeNode *node, Intake *intake);\n        Intake *getIntake(IntakeNode *intake) const;\n\n        void addExhaust(ExhaustSystemNode *node, ExhaustSystem *exhaust);\n        ExhaustSystem *getExhaust(ExhaustSystemNode *exhaust) const;\n\n        void addFunction(FunctionNode *node, Function *function);\n        Function *getFunction(FunctionNode *node) const;\n\n        void addImpulseResponse(ImpulseResponseNode *node, ImpulseResponse *impulse);\n        ImpulseResponse *getImpulseResponse(ImpulseResponseNode *node) const;\n\n        void addCrankshaft(CrankshaftNode *node, Crankshaft *crankshaft);\n        Crankshaft *getCrankshaft(CrankshaftNode *node) const;\n\n        void addConnectingRod(ConnectingRodNode *node, ConnectingRod *rod);\n        ConnectingRod *getConnectingRod(ConnectingRodNode *node) const;\n\n        void setEngine(Engine *engine) { m_engine = engine; }\n        Engine *getEngine() const { return m_engine; }\n\n        void setCylinderIndex(const CylinderBankNode *bank, int localIndex, int globalIndex);\n        int getCylinderIndex(const CylinderBankNode *bank, int localIndex) const;\n\n    protected:\n        Engine *m_engine = nullptr;\n        std::map<CylinderHeadNode *, CylinderHead *> m_heads;\n        std::map<CylinderBankNode *, CylinderBank *> m_banks;\n        std::map<ExhaustSystemNode *, ExhaustSystem *> m_exhaustSystems;\n        std::map<IntakeNode *, Intake *> m_intakes;\n        std::map<FunctionNode *, Function *> m_functions;\n        std::map<ImpulseResponseNode *, ImpulseResponse *> m_impulseResponses;\n        std::map<RodJournalNode *, int> m_rodJournals;\n        std::map<CrankshaftNode *, Crankshaft *> m_crankshafts;\n        std::map<ConnectingRodNode *, ConnectingRod *> m_rods;\n        std::map<std::pair<const CylinderBankNode *, int>, int> m_cylinderIndices;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_ENGINE_CONTEXT_H */\n\n"
  },
  {
    "path": "scripting/include/engine_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_ENGINE_NODE_H\n#define ATG_ENGINE_SIM_ENGINE_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"crankshaft_node.h\"\n#include \"cylinder_bank_node.h\"\n#include \"ignition_module_node.h\"\n#include \"engine_context.h\"\n#include \"fuel_node.h\"\n#include \"throttle_nodes.h\"\n\n#include \"engine_sim.h\"\n\n#include <map>\n#include <set>\n\nnamespace es_script {\n\n    class EngineNode : public ObjectReferenceNode<EngineNode> {\n    public:\n        EngineNode() { /* void */ }\n        virtual ~EngineNode() { /* void */ }\n\n        void buildEngine(Engine *engine) {\n            int cylinderCount = 0;\n            for (const CylinderBankNode *bank : m_cylinderBanks) {\n                cylinderCount += bank->getCylinderCount();\n            }\n\n            std::set<ExhaustSystemNode *> exhaustSystems;\n            std::set<IntakeNode *> intakes;\n            for (const CylinderBankNode *bank : m_cylinderBanks) {\n                const int n = bank->getCylinderCount();\n                for (int i = 0; i < n; ++i) {\n                    exhaustSystems.insert(bank->getCylinder(i).exhaust);\n                    intakes.insert(bank->getCylinder(i).intake);\n                }\n            }\n\n            EngineContext context;\n            context.setEngine(engine);\n\n            Engine::Parameters parameters = m_parameters;\n            parameters.crankshaftCount = (int)m_crankshafts.size();\n            parameters.cylinderBanks = (int)m_cylinderBanks.size();\n            parameters.cylinderCount = cylinderCount;\n            parameters.exhaustSystemCount = (int)exhaustSystems.size();\n            parameters.intakeCount = (int)intakes.size();\n            parameters.throttle = m_throttle->generate();\n            engine->initialize(parameters);\n\n            {\n                int i = 0;\n                for (ExhaustSystemNode *exhaust : exhaustSystems) {\n                    context.addExhaust(\n                        exhaust, engine->getExhaustSystem(i++));\n                }\n            }\n\n            {\n                int i = 0;\n                for (IntakeNode *intake : intakes) {\n                    context.addIntake(\n                        intake, engine->getIntake(i++));\n                }\n            }\n\n            {\n                int i = 0;\n                for (const CylinderBankNode *bank : m_cylinderBanks) {\n                    context.addHead(bank->getCylinderHead(), engine->getHead(i++));\n                }\n            }\n\n            for (const CylinderBankNode *bank : m_cylinderBanks) {\n                const int n = bank->getCylinderCount();\n                for (int i = 0; i < n; ++i) {\n                    exhaustSystems.insert(bank->getCylinder(i).exhaust);\n                    intakes.insert(bank->getCylinder(i).intake);\n                }\n            }\n\n            for (int i = 0; i < parameters.crankshaftCount; ++i) {\n                m_crankshafts[i]->generate(engine->getCrankshaft(i), &context);\n            }\n\n            for (int i = 0; i < parameters.cylinderBanks; ++i) {\n                m_cylinderBanks[i]->indexSlaveJournals(&context);\n            }\n\n            int cylinderIndex = 0;\n            for (int i = 0; i < parameters.cylinderBanks; ++i) {\n                m_cylinderBanks[i]->generate(\n                    i,\n                    cylinderIndex,\n                    engine->getCylinderBank(i),\n                    engine->getCrankshaft(0),\n                    engine,\n                    &context);\n                cylinderIndex += m_cylinderBanks[i]->getCylinderCount();\n            }\n\n            for (int i = 0; i < parameters.cylinderBanks; ++i) {\n                m_cylinderBanks[i]->connectRodAssemblies(&context);\n            }\n\n            m_ignitionModule->generate(engine, &context);\n            \n            Function *meanPistonSpeedToTurbulence = new Function;\n            meanPistonSpeedToTurbulence->initialize(30, 1);\n            for (int i = 0; i < 30; ++i) {\n                const double s = (double)i;\n                meanPistonSpeedToTurbulence->addSample(s, s * 0.5);\n            }\n\n            Fuel *fuel = engine->getFuel();\n            m_fuel->generate(fuel, &context);\n\n            CombustionChamber::Parameters ccParams;\n            ccParams.CrankcasePressure = units::pressure(1.0, units::atm);\n            ccParams.Fuel = fuel;\n            ccParams.StartingPressure = units::pressure(1.0, units::atm);\n            ccParams.StartingTemperature = units::celcius(25.0);\n            ccParams.MeanPistonSpeedToTurbulence = meanPistonSpeedToTurbulence;\n\n            for (int i = 0; i < engine->getCylinderCount(); ++i) {\n                ccParams.Piston = engine->getPiston(i);\n                ccParams.Head = engine->getHead(ccParams.Piston->getCylinderBank()->getIndex());\n                engine->getChamber(i)->initialize(ccParams);\n            }\n        }\n\n        void addCrankshaft(CrankshaftNode *crankshaft) {\n            m_crankshafts.push_back(crankshaft);\n        }\n\n        void addCylinderBank(CylinderBankNode *bank) {\n            m_cylinderBanks.push_back(bank);\n        }\n\n        int getIgnitionModuleCount() const {\n            return m_ignitionModule == nullptr\n                ? 0\n                : 1;\n        }\n\n        void addIgnitionModule(IgnitionModuleNode *ignitionModule) {\n            m_ignitionModule = ignitionModule;\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"name\", &m_parameters.name);\n            addInput(\"starter_torque\", &m_parameters.starterTorque);\n            addInput(\"starter_speed\", &m_parameters.starterSpeed);\n            addInput(\"dyno_min_speed\", &m_parameters.dynoMinSpeed);\n            addInput(\"dyno_max_speed\", &m_parameters.dynoMaxSpeed);\n            addInput(\"dyno_hold_step\", &m_parameters.dynoHoldStep);\n            addInput(\"redline\", &m_parameters.redline);\n            addInput(\"fuel\", &m_fuel, InputTarget::Type::Object);\n            addInput(\"throttle\", &m_throttle, InputTarget::Type::Object);\n            addInput(\"simulation_frequency\", &m_parameters.initialSimulationFrequency);\n            addInput(\"hf_gain\", &m_parameters.initialHighFrequencyGain);\n            addInput(\"jitter\", &m_parameters.initialJitter);\n            addInput(\"noise\", &m_parameters.initialNoise);\n\n            ObjectReferenceNode<EngineNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n        }\n\n        ThrottleNode *m_throttle = nullptr;\n        IgnitionModuleNode *m_ignitionModule = nullptr;\n        FuelNode *m_fuel = nullptr;\n\n        Engine::Parameters m_parameters;\n        std::vector<CrankshaftNode *> m_crankshafts;\n        std::vector<CylinderBankNode *> m_cylinderBanks;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_ENGINE_NODE_H */\n"
  },
  {
    "path": "scripting/include/engine_sim.h",
    "content": "#ifndef ATG_ENGINE_SIM_ENGINE_SIM_H\n#define ATG_ENGINE_SIM_ENGINE_SIM_H\n\n#include \"../../include/engine.h\"\n#include \"../../include/crankshaft.h\"\n#include \"../../include/connecting_rod.h\"\n#include \"../../include/cylinder_bank.h\"\n#include \"../../include/piston.h\"\n#include \"../../include/function.h\"\n#include \"../../include/vehicle.h\"\n#include \"../../include/transmission.h\"\n#include \"../../include/simulator.h\"\n#include \"../../include/fuel.h\"\n#include \"../../include/impulse_response.h\"\n#include \"../../include/standard_valvetrain.h\"\n#include \"../../include/vtec_valvetrain.h\"\n#include \"../../include/application_settings.h\"\n#include \"../../include/throttle.h\"\n#include \"../../include/direct_throttle_linkage.h\"\n#include \"../../include/governor.h\"\n\n#endif /* ATG_ENGINE_SIM_ENGINE_SIM_H */"
  },
  {
    "path": "scripting/include/exhaust_system_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_EXHAUST_SYSTEM_NODE_H\n#define ATG_ENGINE_SIM_EXHAUST_SYSTEM_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"engine_context.h\"\n#include \"impulse_response_node.h\"\n\n#include \"engine_sim.h\"\n\nnamespace es_script {\n\n    class ExhaustSystemNode : public ObjectReferenceNode<ExhaustSystemNode> {\n    public:\n        ExhaustSystemNode() { /* void */ }\n        virtual ~ExhaustSystemNode() { /* void */ }\n\n        ExhaustSystem *generate(EngineContext *context) {\n            ExhaustSystem *exhaust = context->getExhaust(this);\n            ExhaustSystem::Parameters parameters = m_parameters;\n            parameters.impulseResponse = m_impulseResponse->generate(context);\n\n            exhaust->initialize(parameters);\n\n            return exhaust;\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"length\", &m_parameters.length);\n            addInput(\"collector_cross_section_area\", &m_parameters.collectorCrossSectionArea);\n            addInput(\"outlet_flow_rate\", &m_parameters.outletFlowRate);\n            addInput(\"primary_tube_length\", &m_parameters.primaryTubeLength);\n            addInput(\"primary_flow_rate\", &m_parameters.primaryFlowRate);\n            addInput(\"audio_volume\", &m_parameters.audioVolume);\n            addInput(\"velocity_decay\", &m_parameters.velocityDecay);\n            addInput(\"impulse_response\", &m_impulseResponse, InputTarget::Type::Object);\n\n            ObjectReferenceNode<ExhaustSystemNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n        }\n\n        ImpulseResponseNode *m_impulseResponse = nullptr;\n        ExhaustSystem::Parameters m_parameters;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_EXHAUST_SYSTEM_NODE_H */\n"
  },
  {
    "path": "scripting/include/fuel_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_FUEL_NODE_H\n#define ATG_ENGINE_SIM_FUEL_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"engine_context.h\"\n#include \"function_node.h\"\n\n#include \"engine_sim.h\"\n\n#include <map>\n#include <vector>\n\nnamespace es_script {\n\n    class FuelNode : public ObjectReferenceNode<FuelNode> {\n    public:\n        FuelNode() { /* void */ }\n        virtual ~FuelNode() { /* void */ }\n\n        void generate(Fuel *fuel, EngineContext *context) const {\n            Fuel::Parameters params = m_parameters;\n            params.turbulenceToFlameSpeedRatio =\n                m_turbulenceToFlameSpeedRatio->generate(context);\n\n            fuel->initialize(params);\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\n                \"turbulence_to_flame_speed_ratio\",\n                &m_turbulenceToFlameSpeedRatio,\n                InputTarget::Type::Object);\n            addInput(\"name\", &m_parameters.name);\n            addInput(\"molecular_mass\", &m_parameters.molecularMass);\n            addInput(\"energy_density\", &m_parameters.energyDensity);\n            addInput(\"density\", &m_parameters.density);\n            addInput(\"molecular_afr\", &m_parameters.molecularAfr);\n            addInput(\"max_burning_efficiency\", &m_parameters.maxBurningEfficiency);\n            addInput(\"burning_efficiency_randomness\", &m_parameters.burningEfficiencyRandomness);\n            addInput(\"low_efficiency_attenuation\", &m_parameters.lowEfficiencyAttenuation);\n            addInput(\"max_turbulence_effect\", &m_parameters.maxTurbulenceEffect);\n            addInput(\"max_dilution_effect\", &m_parameters.maxDilutionEffect);\n\n            ObjectReferenceNode<FuelNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n        }\n\n        FunctionNode *m_turbulenceToFlameSpeedRatio = nullptr;\n        Fuel::Parameters m_parameters;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_FUEL_NODE_H */\n"
  },
  {
    "path": "scripting/include/function_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_FUNCTION_NODE_H\n#define ATG_ENGINE_SIM_FUNCTION_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"engine_context.h\"\n\n#include \"engine_sim.h\"\n\nnamespace es_script {\n\n    class FunctionNode : public ObjectReferenceNode<FunctionNode> {\n    public:\n        struct Sample {\n            double x, y;\n        };\n\n    public:\n        FunctionNode() { /* void */ }\n        virtual ~FunctionNode() { /* void */ }\n\n        void addSample(double x, double y) {\n            m_samples.push_back({ x, y });\n        }\n\n        void setFilterRadius(double filterRadius) {\n            m_filterRadius = filterRadius;\n        }\n\n        virtual Function *generate(EngineContext *context) {\n            Function *existingFunction = context->getFunction(this);\n            if (existingFunction != nullptr) {\n                return existingFunction;\n            }\n            else {\n                Function *function = new Function;\n                function->initialize((int)m_samples.size(), m_filterRadius);\n\n                for (const Sample &sample : m_samples) {\n                    function->addSample(sample.x, sample.y);\n                }\n\n                context->addFunction(this, function);\n                return function;\n            }\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"filter_radius\", &m_filterRadius);\n\n            ObjectReferenceNode<FunctionNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n        }\n\n        std::vector<Sample> m_samples;\n        double m_filterRadius = 0.0;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_FUNCTION_NODE_H */\n"
  },
  {
    "path": "scripting/include/ignition_module_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_IGNITION_MODULE_NODE_H\n#define ATG_ENGINE_SIM_IGNITION_MODULE_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"engine_context.h\"\n#include \"function_node.h\"\n\n#include \"engine_sim.h\"\n\n#include <map>\n\nnamespace es_script {\n\n    class IgnitionModuleNode : public ObjectReferenceNode<IgnitionModuleNode> {\n    public:\n        struct Post {\n            IgnitionWireNode *wire;\n            double angle;\n        };\n\n    public:\n        IgnitionModuleNode() { /* void */ }\n        virtual ~IgnitionModuleNode() { /* void */ }\n\n        void generate(Engine *engine, EngineContext *context) const {\n            IgnitionModule::Parameters params;\n            params.crankshaft = engine->getCrankshaft(0);\n            params.cylinderCount = engine->getCylinderCount();\n            params.revLimit = m_revLimit;\n            params.timingCurve = m_timingCurve->generate(context);\n            params.cylinderCount = engine->getCylinderCount();\n            params.limiterDuration = m_limiterDuration;\n            engine->getIgnitionModule()->initialize(params);\n\n            for (const Post &post : m_posts) {\n                std::set<IgnitionWireNode::Connection> connections =\n                    post.wire->getConnections();\n                for (const IgnitionWireNode::Connection &connection : connections) {\n                    const int index = context->getCylinderIndex(\n                        connection.first,\n                        connection.second\n                    );\n\n                    engine->getIgnitionModule()->setFiringOrder(index, post.angle);\n                }\n            }\n        }\n\n        void connect(IgnitionWireNode *wire, double angle) {\n            m_posts.push_back({ wire, angle });\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"timing_curve\", &m_timingCurve);\n            addInput(\"rev_limit\", &m_revLimit);\n            addInput(\"limiter_duration\", &m_limiterDuration);\n\n            ObjectReferenceNode<IgnitionModuleNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n        }\n\n        double m_revLimit = 0.0;\n        FunctionNode *m_timingCurve = nullptr;\n        double m_limiterDuration = 0.0;\n        std::vector<Post> m_posts;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_IGNITION_MODULE_NODE_H */\n"
  },
  {
    "path": "scripting/include/ignition_wire_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_IGNITION_WIRE_NODE_H\n#define ATG_ENGINE_SIM_IGNITION_WIRE_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"engine_context.h\"\n#include \"function_node.h\"\n\n#include \"engine_sim.h\"\n\n#include <set>\n\nnamespace es_script {\n\n    class IgnitionWireNode : public ObjectReferenceNode<IgnitionWireNode> {\n    public:\n        using Connection = std::pair<CylinderBankNode *, int>;\n\n    public:\n        IgnitionWireNode() { /* void */ }\n        virtual ~IgnitionWireNode() { /* void */ }\n\n        void connect(CylinderBankNode *bank, int i) {\n            m_connections.insert({ bank, i });\n        }\n\n        std::set<Connection> getConnections() const {\n            return m_connections;\n        }\n\n    protected:\n        virtual void registerInputs() {\n            ObjectReferenceNode<IgnitionWireNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n        }\n\n        std::set<Connection> m_connections;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_IGNITION_WIRE_NODE_H */\n"
  },
  {
    "path": "scripting/include/impulse_response_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_IMPULSE_RESPONSE_NODE_H\n#define ATG_ENGINE_SIM_IMPULSE_RESPONSE_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"engine_context.h\"\n#include \"impulse_response_node.h\"\n\n#include \"engine_sim.h\"\n\n#include <map>\n#include <vector>\n\nnamespace es_script {\n\n    class ImpulseResponseNode\n        : public ObjectReferenceNode<ImpulseResponseNode> {\n    public:\n        ImpulseResponseNode() { /* void */ }\n        virtual ~ImpulseResponseNode() { /* void */ }\n\n        ImpulseResponse *generate(EngineContext *context) {\n            ImpulseResponse *existingIr = context->getImpulseResponse(this);\n            if (existingIr != nullptr) {\n                return existingIr;\n            }\n            else {\n                piranha::Path path = m_filename;\n                piranha::Path parentPath;\n                m_irStructure->getParentUnit()->getPath().getParentPath(&parentPath);\n                if (!path.isAbsolute()) {\n                    path = parentPath.append(path);\n                }\n\n                ImpulseResponse *impulseResponse = new ImpulseResponse;\n                impulseResponse->initialize(\n                    path.toString(),\n                    m_volume);\n\n                return impulseResponse;\n            }\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"filename\", &m_filename);\n            addInput(\"volume\", &m_volume);\n\n            ObjectReferenceNode<ImpulseResponseNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n        }\n\n        std::string m_filename = \"\";\n        double m_volume = 1.0;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_IMPULSE_RESPONSE_NODE_H */\n"
  },
  {
    "path": "scripting/include/intake_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_INTAKE_NODE_H\n#define ATG_ENGINE_SIM_INTAKE_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"engine_context.h\"\n#include \"function_node.h\"\n\n#include \"engine_sim.h\"\n\n#include <map>\n#include <vector>\n\nnamespace es_script {\n\n    class IntakeNode : public ObjectReferenceNode<IntakeNode> {\n    public:\n        IntakeNode() { /* void */ }\n        virtual ~IntakeNode() { /* void */ }\n\n        Intake *generate(EngineContext *context) {\n            Intake *intake = context->getIntake(this);\n            Intake::Parameters parameters = m_parameters;\n            intake->initialize(parameters);\n\n            return intake;\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"plenum_volume\", &m_parameters.volume);\n            addInput(\"plenum_cross_section_area\", &m_parameters.CrossSectionArea);\n            addInput(\"intake_flow_rate\", &m_parameters.InputFlowK);\n            addInput(\"idle_flow_rate\", &m_parameters.IdleFlowK);\n            addInput(\"runner_flow_rate\", &m_parameters.RunnerFlowRate);\n            addInput(\"molecular_afr\", &m_parameters.MolecularAfr);\n            addInput(\"idle_throttle_plate_position\", &m_parameters.IdleThrottlePlatePosition);\n            addInput(\"throttle_gamma\", &m_throttleGammaUnused);\n            addInput(\"runner_length\", &m_parameters.RunnerLength);\n            addInput(\"velocity_decay\", &m_parameters.VelocityDecay);\n\n            ObjectReferenceNode<IntakeNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n        }\n\n        double m_throttleGammaUnused = 0.0; // Deprecated; to be removed in a future release\n        Intake::Parameters m_parameters;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_INTAKE_NODE_H */\n"
  },
  {
    "path": "scripting/include/language_rules.h",
    "content": "#ifndef ATG_ENGINE_SIM_LANGUAGE_RULES_H\n#define ATG_ENGINE_SIM_LANGUAGE_RULES_H\n\n#include \"piranha.h\"\n\nnamespace es_script {\n\n    class LanguageRules : public piranha::LanguageRules {\n    public:\n        LanguageRules();\n        ~LanguageRules();\n\n    protected:\n        virtual void registerBuiltinNodeTypes();\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_LANGUAGE_RULES_H */\n"
  },
  {
    "path": "scripting/include/node.h",
    "content": "#ifndef ATG_ENGINE_SIM_NODE_H\n#define ATG_ENGINE_SIM_NODE_H\n\n#include \"piranha.h\"\n\n#include <map>\n#include <string>\n\nnamespace es_script {\n\n    class Node : public piranha::Node {\n    protected:\n        struct InputTarget {\n            enum class Type {\n                Object,\n                Atomic\n            };\n\n            piranha::pNodeInput *input = nullptr;\n            void *memoryTarget = nullptr;\n            Type type = Type::Atomic;\n        };\n\n    public:\n        Node() {\n            /* void */\n        }\n\n        virtual ~Node() {\n            for (auto i : m_inputMap) {\n                delete i.second.input;\n            }\n        }\n\n        template <typename T_Out>\n        T_Out readAtomicInput(const std::string &name) {\n            T_Out out;\n            (*m_inputMap[name].input)->fullCompute(&out);\n\n            return out;\n        }\n\n        void readAllInputs() {\n            for (auto i : m_inputMap) {\n                if (i.second.type == InputTarget::Type::Atomic\n                    || i.second.type == InputTarget::Type::Object)\n                {\n                    (*m_inputMap[i.first].input)->fullCompute(i.second.memoryTarget);\n                }\n            }\n        }\n\n        void addInput(\n            const std::string &name,\n            void *target,\n            InputTarget::Type type = InputTarget::Type::Atomic)\n        {\n            m_inputMap[name] = {\n                new piranha::pNodeInput,\n                target,\n                type\n            };\n        }\n\n        virtual void registerInputs() {\n            for (auto i : m_inputMap) {\n                registerInput(i.second.input, i.first);\n            }\n        }\n\n    private:\n        std::map<std::string, InputTarget> m_inputMap;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_NODE_H */\n"
  },
  {
    "path": "scripting/include/object_reference_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_OBJECT_REFERENCE_H\n#define ATG_ENGINE_SIM_OBJECT_REFERENCE_H\n\n#include \"node.h\"\n\n#include \"object_reference_node_output.h\"\n\nnamespace es_script {\n\n    template <typename Type>\n    class ObjectReferenceNode : public Node {\n    public:\n        ObjectReferenceNode() {\n            /* void */\n        }\n\n        ~ObjectReferenceNode() {\n            /* void */\n        }\n\n        template <typename Type>\n        void overrideType() {\n            m_output.overrideType(LookupChannelType<Type>());\n        }\n\n    protected:\n        virtual void registerOutputs() {\n            setPrimaryOutput(\"__out\");\n            registerOutput(&m_output, \"__out\");\n        }\n\n        virtual void _evaluate() {\n            m_output.setReference(nullptr);\n        }\n\n        void setOutput(Type *output) { m_output.setReference(output); }\n\n        ObjectReferenceNodeOutput<Type> m_output;\n    };\n\n    template <typename T_ReferenceType>\n    using NullReferenceNode = ObjectReferenceNode<T_ReferenceType>;\n\n} /* namespace manta */\n\n#endif /* ATG_ENGINE_SIM_OBJECT_REFERENCE_H */\n"
  },
  {
    "path": "scripting/include/object_reference_node_output.h",
    "content": "#ifndef ATG_ENGINE_SIM_OBJECT_REFERENCE_NODE_OUTPUT_H\n#define ATG_ENGINE_SIM_OBJECT_REFERENCE_NODE_OUTPUT_H\n\n#include \"piranha.h\"\n\n#include \"channel_types.h\"\n\nnamespace es_script {\n\n    template <typename Type>\n    class ObjectReferenceNodeOutput : public piranha::NodeOutput {\n    public:\n        ObjectReferenceNodeOutput() : NodeOutput(LookupChannelType<Type>()) {\n            m_data = nullptr;\n        }\n\n        virtual ~ObjectReferenceNodeOutput() {\n            /* void */\n        }\n\n        virtual void fullCompute(void *target) const {\n            *reinterpret_cast<Type **>(target) = m_data;\n        }\n\n        Type *getReference() const { return m_data; }\n        void setReference(Type *data) { m_data = data; }\n\n    protected:\n        Type *m_data;\n    };\n\n    template <typename Type>\n    Type *getObject(piranha::pNodeInput input) {\n        return static_cast<ObjectReferenceNodeOutput<Type> *>(input)->getReference();\n    }\n\n} /* namespace manta */\n\n#endif /* ATG_ENGINE_SIM_OBJECT_REFERENCE_NODE_OUTPUT_H */\n"
  },
  {
    "path": "scripting/include/piranha.h",
    "content": "#ifndef ATG_ENGINE_SIM_PIRANHA_H\n#define ATG_ENGINE_SIM_PIRANHA_H\n\n#include <piranha/include/piranha.h>\n\n#endif /* ATG_ENGINE_SIM_PIRANHA_H */\n"
  },
  {
    "path": "scripting/include/piston_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_PISTON_NODE_H\n#define ATG_ENGINE_SIM_PISTON_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"rod_journal_node.h\"\n#include \"piston_node.h\"\n\n#include \"engine_sim.h\"\n\n#include <map>\n#include <vector>\n\nnamespace es_script {\n\n    class PistonNode : public ObjectReferenceNode<PistonNode> {\n    public:\n        PistonNode() { /* void */ }\n        virtual ~PistonNode() { /* void */ }\n\n        void generate(\n            Piston *piston,\n            ConnectingRod *rod,\n            CylinderBank *cylinderBank,\n            int cylinderIndex) const\n        {\n            Piston::Parameters params = m_parameters;\n            params.Bank = cylinderBank;\n            params.CylinderIndex = cylinderIndex;\n            params.Rod = rod;\n\n            piston->initialize(params);\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"mass\", &m_parameters.mass);\n            addInput(\"blowby\", &m_parameters.BlowbyFlowCoefficient);\n            addInput(\"compression_height\", &m_parameters.CompressionHeight);\n            addInput(\"wrist_pin_position\", &m_parameters.WristPinPosition);\n            addInput(\"displacement\", &m_parameters.Displacement);\n\n            ObjectReferenceNode<PistonNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n\n            m_parameters.Bank = nullptr;\n            m_parameters.Rod = nullptr;\n            m_parameters.CylinderIndex = 0;\n        }\n\n        Piston::Parameters m_parameters;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_PISTON_NODE_H */\n"
  },
  {
    "path": "scripting/include/rod_journal_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_ROD_JOURNAL_NODE_H\n#define ATG_ENGINE_SIM_ROD_JOURNAL_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"engine_sim.h\"\n\n#include <map>\n\nnamespace es_script {\n\n    class CrankshaftNode;\n    class ConnectingRodNode;\n    class RodJournalNode : public ObjectReferenceNode<RodJournalNode> {\n        friend CrankshaftNode;\n        friend ConnectingRodNode;\n\n    public:\n        RodJournalNode() { /* void */ }\n        virtual ~RodJournalNode() { /* void */ }\n\n        double getAngle() const {\n            return m_angle;\n        }\n\n        CrankshaftNode *getCrankshaft() const {\n            return m_crankshaft;\n        }\n\n        ConnectingRodNode *getRod() const {\n            return m_rod;\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"angle\", &m_angle);\n\n            ObjectReferenceNode<RodJournalNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n        }\n\n        CrankshaftNode *m_crankshaft = nullptr;\n        ConnectingRodNode *m_rod = nullptr;\n        double m_angle = 0.0;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_ROD_JOURNAL_NODE_H */\n"
  },
  {
    "path": "scripting/include/standard_valvetrain_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_STANDARD_VALVETRAIN_NODE_H\n#define ATG_ENGINE_SIM_STANDARD_VALVETRAIN_NODE_H\n\n#include \"valvetrain_node.h\"\n\n#include \"camshaft_node.h\"\n\n#include \"engine_sim.h\"\n\nnamespace es_script {\n\n    class StandardValvetrainNode : public ValvetrainNode {\n    public:\n        StandardValvetrainNode() { /* void */ }\n        virtual ~StandardValvetrainNode() { /* void */ }\n\n        virtual Valvetrain *generate(\n            EngineContext *context,\n            Crankshaft *crank) override\n        {\n            StandardValvetrain *valvetrain = new StandardValvetrain;\n\n            Camshaft\n                *intakeCam = new Camshaft(),\n                *exhaustCam = new Camshaft();\n\n            m_intakeCamshaft->generate(intakeCam, crank, context);\n            m_exhaustCamshaft->generate(exhaustCam, crank, context);\n\n            StandardValvetrain::Parameters params;\n            params.intakeCamshaft = intakeCam;\n            params.exhaustCamshaft = exhaustCam;\n            valvetrain->initialize(params);\n\n            return valvetrain;\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"intake_camshaft\", &m_intakeCamshaft, InputTarget::Type::Object);\n            addInput(\"exhaust_camshaft\", &m_exhaustCamshaft, InputTarget::Type::Object);\n\n            ValvetrainNode::registerInputs();\n        }\n\n    private:\n        CamshaftNode *m_intakeCamshaft = nullptr;\n        CamshaftNode *m_exhaustCamshaft = nullptr;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_STANDARD_VALVETRAIN_NODE_H */\n"
  },
  {
    "path": "scripting/include/throttle_nodes.h",
    "content": "#ifndef ATG_ENGINE_SIM_THROTTLE_NODES_H\n#define ATG_ENGINE_SIM_THROTTLE_NODES_H\n\n#include \"object_reference_node.h\"\n\n#include \"engine_sim.h\"\n\nnamespace es_script {\n\n    class ThrottleNode : public ObjectReferenceNode<ThrottleNode> {\n    public:\n        ThrottleNode() { /* void */ }\n        virtual ~ThrottleNode() { /* void */ }\n\n        virtual Throttle *generate() const = 0;\n\n    protected:\n        virtual void _evaluate() {\n            setOutput(this);\n            readAllInputs();\n        }\n    };\n\n    class DirectThrottleLinkageNode : public ThrottleNode {\n    public:\n        DirectThrottleLinkageNode() { /* void */ }\n        virtual ~DirectThrottleLinkageNode() { /* void */ }\n\n        virtual Throttle *generate() const override {\n            DirectThrottleLinkage *throttle = new DirectThrottleLinkage;\n            throttle->initialize(m_parameters);\n\n            return static_cast<Throttle *>(throttle);\n        }\n\n    protected:\n        virtual void registerInputs() override {\n            addInput(\"gamma\", &m_parameters.gamma);\n\n            ThrottleNode::registerInputs();\n        }\n\n        DirectThrottleLinkage::Parameters m_parameters;\n    };\n\n    class GovernorNode : public ThrottleNode {\n    public:\n        GovernorNode() { /* void */ }\n        virtual ~GovernorNode() { /* void */ }\n\n        virtual Throttle *generate() const override {\n            Governor *throttle = new Governor;\n            throttle->initialize(m_parameters);\n\n            return static_cast<Throttle *>(throttle);\n        }\n\n    protected:\n        virtual void registerInputs() override {\n            addInput(\"min_speed\", &m_parameters.minSpeed);\n            addInput(\"max_speed\", &m_parameters.maxSpeed);\n            addInput(\"min_v\", &m_parameters.minVelocity);\n            addInput(\"max_v\", &m_parameters.maxVelocity);\n            addInput(\"k_s\", &m_parameters.k_s);\n            addInput(\"k_d\", &m_parameters.k_d);\n            addInput(\"gamma\", &m_parameters.gamma);\n\n            ThrottleNode::registerInputs();\n        }\n\n        Governor::Parameters m_parameters;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_THROTTLE_NODES_H */\n"
  },
  {
    "path": "scripting/include/transmission_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_TRANSMISSION_NODE_H\n#define ATG_ENGINE_SIM_TRANSMISSION_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"engine_sim.h\"\n\n#include <vector>\n\nnamespace es_script {\n\n    class TransmissionNode : public ObjectReferenceNode<TransmissionNode> {\n    public:\n        TransmissionNode() { /* void */ }\n        virtual ~TransmissionNode() { /* void */ }\n\n        void generate(Transmission *transmission) const {\n            Transmission::Parameters parameters = m_parameters;\n            parameters.GearCount = static_cast<int>(m_gears.size());\n            parameters.GearRatios = m_gears.data();\n\n            transmission->initialize(parameters);\n        }\n\n        void addGear(double ratio) {\n            m_gears.push_back(ratio);\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"max_clutch_torque\", &m_parameters.MaxClutchTorque);\n\n            ObjectReferenceNode<TransmissionNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n\n            // Read inputs\n            readAllInputs();\n        }\n\n        Transmission::Parameters m_parameters;\n        std::vector<double> m_gears;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_TRANSMISSION_NODE_H */\n"
  },
  {
    "path": "scripting/include/valvetrain_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_VALVETRAIN_NODE_H\n#define ATG_ENGINE_SIM_VALVETRAIN_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"engine_context.h\"\n\nnamespace es_script {\n\n    class ValvetrainNode : public ObjectReferenceNode<ValvetrainNode> {\n    public:\n        ValvetrainNode() { /* void */ }\n        virtual ~ValvetrainNode() { /* void */ }\n\n        virtual Valvetrain *generate(EngineContext *context, Crankshaft *crank) = 0;\n\n    protected:\n        virtual void registerInputs() {\n            ObjectReferenceNode<ValvetrainNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {\n            setOutput(this);\n            readAllInputs();\n        }\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_VALVETRAIN_NODE_H */\n"
  },
  {
    "path": "scripting/include/vehicle_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_VEHICLE_NODE_H\n#define ATG_ENGINE_SIM_VEHICLE_NODE_H\n\n#include \"object_reference_node.h\"\n\n#include \"engine_sim.h\"\n\nnamespace es_script {\n\n    class VehicleNode : public ObjectReferenceNode<VehicleNode> {\n    public:\n        VehicleNode() { /* void */ }\n        virtual ~VehicleNode() { /* void */ }\n\n        void generate(Vehicle *vehicle) const\n        {\n            vehicle->initialize(m_parameters);\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"mass\", &m_parameters.mass);\n            addInput(\"drag_coefficient\", &m_parameters.dragCoefficient);\n            addInput(\"cross_sectional_area\", &m_parameters.crossSectionArea);\n            addInput(\"diff_ratio\", &m_parameters.diffRatio);\n            addInput(\"tire_radius\", &m_parameters.tireRadius);\n            addInput(\"rolling_resistance\", &m_parameters.rollingResistance);\n\n            ObjectReferenceNode<VehicleNode>::registerInputs();\n        }\n\n        virtual void _evaluate() {           \n            setOutput(this);\n            // Read inputs\n            readAllInputs();\n\n        }\n\n        Vehicle::Parameters m_parameters;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_VEHICLE_NODE_H */\n"
  },
  {
    "path": "scripting/include/vtec_valvetrain_node.h",
    "content": "#ifndef ATG_ENGINE_SIM_VTEC_VALVETRAIN_NODE_H\n#define ATG_ENGINE_SIM_VTEC_VALVETRAIN_NODE_H\n\n#include \"valvetrain_node.h\"\n\n#include \"camshaft_node.h\"\n\n#include \"engine_sim.h\"\n\nnamespace es_script {\n\n    class VtecValvetrainNode : public ValvetrainNode {\n    public:\n        VtecValvetrainNode() { /* void */ }\n        virtual ~VtecValvetrainNode() { /* void */ }\n\n        virtual Valvetrain *generate(\n            EngineContext *context,\n            Crankshaft *crank) override {\n            VtecValvetrain *valvetrain = new VtecValvetrain;\n\n            Camshaft\n                *intakeCam = new Camshaft(),\n                *exhaustCam = new Camshaft(),\n                *vtecIntakeCam = new Camshaft(),\n                *vtecExhaustCam = new Camshaft();\n\n            m_intakeCamshaft->generate(intakeCam, crank, context);\n            m_exhaustCamshaft->generate(exhaustCam, crank, context);\n            m_vtecIntakeCamshaft->generate(vtecIntakeCam, crank, context);\n            m_vtecExhaustCamshaft->generate(vtecExhaustCam, crank, context);\n\n            VtecValvetrain::Parameters params;\n            params.intakeCamshaft = intakeCam;\n            params.exhaustCamshaft = exhaustCam;\n            params.vtecIntakeCamshaft = vtecIntakeCam;\n            params.vtexExhaustCamshaft = vtecExhaustCam;\n            params.minRpm = m_parameters.minRpm;\n            params.minSpeed = m_parameters.minSpeed;\n            params.minThrottlePosition = m_parameters.minThrottlePosition;\n            params.manifoldVacuum = m_parameters.manifoldVacuum;\n            params.engine = context->getEngine();\n            valvetrain->initialize(params);\n\n            return valvetrain;\n        }\n\n    protected:\n        virtual void registerInputs() {\n            addInput(\"vtec_intake_camshaft\", &m_vtecIntakeCamshaft, InputTarget::Type::Object);\n            addInput(\"vtec_exhaust_camshaft\", &m_vtecExhaustCamshaft, InputTarget::Type::Object);\n            addInput(\"intake_camshaft\", &m_intakeCamshaft, InputTarget::Type::Object);\n            addInput(\"exhaust_camshaft\", &m_exhaustCamshaft, InputTarget::Type::Object);\n\n            addInput(\"min_rpm\", &m_parameters.minRpm);\n            addInput(\"min_speed\", &m_parameters.minSpeed);\n            addInput(\"manifold_vacuum\", &m_parameters.manifoldVacuum);\n            addInput(\"min_throttle_position\", &m_parameters.minThrottlePosition);\n\n            ValvetrainNode::registerInputs();\n        }\n\n    private:\n        CamshaftNode *m_vtecIntakeCamshaft = nullptr;\n        CamshaftNode *m_vtecExhaustCamshaft = nullptr;\n        CamshaftNode *m_intakeCamshaft = nullptr;\n        CamshaftNode *m_exhaustCamshaft = nullptr;\n\n        VtecValvetrain::Parameters m_parameters;\n    };\n\n} /* namespace es_script */\n\n#endif /* ATG_ENGINE_SIM_VTEC_VALVETRAIN_NODE_H */\n"
  },
  {
    "path": "scripting/src/channel_types.cpp",
    "content": "#include \"../include/channel_types.h\"\n\n#define DEFINE_CHANNEL(channel_type) const piranha::ChannelType es_script::ObjectChannel::channel_type(#channel_type);\n\nDEFINE_CHANNEL(EngineChannel);\nDEFINE_CHANNEL(CrankshaftChannel);\nDEFINE_CHANNEL(RodJournalChannel);\nDEFINE_CHANNEL(ConnectingRodChannel);\nDEFINE_CHANNEL(CylinderBankChannel);\nDEFINE_CHANNEL(PistonChannel);\nDEFINE_CHANNEL(FunctionChannel);\nDEFINE_CHANNEL(IntakeChannel);\nDEFINE_CHANNEL(ExhaustSystemChannel);\nDEFINE_CHANNEL(CylinderHeadChannel);\nDEFINE_CHANNEL(CamshaftChannel);\nDEFINE_CHANNEL(IgnitionModuleChannel);\nDEFINE_CHANNEL(IgnitionWireChannel);\nDEFINE_CHANNEL(FuelChannel);\nDEFINE_CHANNEL(ImpulseResponseChannel);\nDEFINE_CHANNEL(ValvetrainChannel);\nDEFINE_CHANNEL(VehicleChannel);\nDEFINE_CHANNEL(TransmissionChannel);\nDEFINE_CHANNEL(ThrottleChannel);\n"
  },
  {
    "path": "scripting/src/compiler.cpp",
    "content": "#include \"../include/compiler.h\"\n\nes_script::Compiler::Output *es_script::Compiler::s_output = nullptr;\n\nes_script::Compiler::Compiler() {\n    m_compiler = nullptr;\n}\n\nes_script::Compiler::~Compiler() {\n    assert(m_compiler == nullptr);\n}\n\nes_script::Compiler::Output *es_script::Compiler::output() {\n    if (s_output == nullptr) {\n        s_output = new Output;\n    }\n\n    return s_output;\n}\n\nvoid es_script::Compiler::initialize() {\n    m_compiler = new piranha::Compiler(&m_rules);\n    m_compiler->setFileExtension(\".mr\");\n\n    m_compiler->addSearchPath(\"../../es/\");\n    m_compiler->addSearchPath(\"../es/\");\n    m_compiler->addSearchPath(\"es/\");\n\n    m_rules.initialize();\n}\n\nbool es_script::Compiler::compile(const piranha::IrPath &path) {\n    bool successful = false;\n\n    std::ofstream file(\"error_log.log\", std::ios::out);\n    piranha::IrCompilationUnit *unit = m_compiler->compile(path);\n    if (unit == nullptr) {\n        file << \"Can't find file: \" << path.toString() << \"\\n\";\n    }\n    else {\n        const piranha::ErrorList *errors = m_compiler->getErrorList();\n        if (errors->getErrorCount() == 0) {\n            unit->build(&m_program);\n\n            m_program.initialize();\n\n            successful = true;\n        }\n        else {\n            for (int i = 0; i < errors->getErrorCount(); ++i) {\n                printError(errors->getCompilationError(i), file);\n            }\n        }\n    }\n\n    file.close();\n\n    return successful;\n}\n\nes_script::Compiler::Output es_script::Compiler::execute() {\n    const bool result = m_program.execute();\n\n    if (!result) {\n        // Todo: Runtime error\n    }\n\n    return *output();\n}\n\nvoid es_script::Compiler::destroy() {\n    m_program.free();\n    m_compiler->free();\n\n    delete m_compiler;\n    m_compiler = nullptr;\n}\n\nvoid es_script::Compiler::printError(\n    const piranha::CompilationError *err,\n    std::ofstream &file) const\n{\n    const piranha::ErrorCode_struct &errorCode = err->getErrorCode();\n    file << err->getCompilationUnit()->getPath().getStem()\n        << \"(\" << err->getErrorLocation()->lineStart << \"): error \"\n        << errorCode.stage << errorCode.code << \": \" << errorCode.info << std::endl;\n\n    piranha::IrContextTree *context = err->getInstantiation();\n    while (context != nullptr) {\n        piranha::IrNode *instance = context->getContext();\n        if (instance != nullptr) {\n            const std::string instanceName = instance->getName();\n            const std::string definitionName = (instance->getDefinition() != nullptr)\n                ? instance->getDefinition()->getName()\n                : \"<Type Error>\";\n            const std::string formattedName = (instanceName.empty())\n                ? \"<unnamed> \" + definitionName\n                : instanceName + \" \" + definitionName;\n\n            file\n                << \"       While instantiating: \"\n                << instance->getParentUnit()->getPath().getStem()\n                << \"(\" << instance->getSummaryToken()->lineStart << \"): \"\n                << formattedName << std::endl;\n        }\n\n        context = context->getParent();\n    }\n}\n"
  },
  {
    "path": "scripting/src/engine_context.cpp",
    "content": "#include \"../include/engine_context.h\"\n\n#include \"../include/cylinder_bank_node.h\"\n\nes_script::EngineContext::EngineContext() {\n    /* void */\n}\n\nes_script::EngineContext::~EngineContext() {\n    /* void */\n}\n\nvoid es_script::EngineContext::addHead(CylinderHeadNode *node, CylinderHead *head) {\n    m_heads[node] = head;\n}\n\nCylinderHead *es_script::EngineContext::getHead(CylinderHeadNode *head) {\n    auto it = m_heads.find(head);\n    if (it != m_heads.end()) {\n        return it->second;\n    }\n    else return nullptr;\n}\n\nvoid es_script::EngineContext::addBank(CylinderBankNode *node, CylinderBank *bank) {\n    m_banks[node] = bank;\n}\n\nCylinderBank *es_script::EngineContext::getBank(CylinderBankNode *bank) const {\n    auto it = m_banks.find(bank);\n    if (it != m_banks.end()) {\n        return it->second;\n    }\n    else return nullptr;\n}\n\nvoid es_script::EngineContext::addRodJournal(RodJournalNode *node, int index) {\n    m_rodJournals[node] = index;\n}\n\nint es_script::EngineContext::getRodJournalIndex(RodJournalNode *node) const {\n    auto it = m_rodJournals.find(node);\n    if (it != m_rodJournals.end()) {\n        return it->second;\n    }\n    else return -1;\n}\n\nvoid es_script::EngineContext::addIntake(IntakeNode *node, Intake *intake) {\n    m_intakes[node] = intake;\n}\n\nIntake *es_script::EngineContext::getIntake(IntakeNode *intake) const {\n    auto it = m_intakes.find(intake);\n    if (it != m_intakes.end()) {\n        return it->second;\n    }\n    else return nullptr;\n}\n\nvoid es_script::EngineContext::addExhaust(\n    ExhaustSystemNode *node,\n    ExhaustSystem *exhaust)\n{\n    m_exhaustSystems[node] = exhaust;\n}\n\nExhaustSystem *es_script::EngineContext::getExhaust(ExhaustSystemNode *exhaust) const {\n    auto it = m_exhaustSystems.find(exhaust);\n    if (it != m_exhaustSystems.end()) {\n        return it->second;\n    }\n    else return nullptr;\n}\n\nvoid es_script::EngineContext::addFunction(FunctionNode *node, Function *function) {\n    m_functions[node] = function;\n}\n\nFunction *es_script::EngineContext::getFunction(FunctionNode *exhaust) const {\n    auto it = m_functions.find(exhaust);\n    if (it != m_functions.end()) {\n        return it->second;\n    }\n    else return nullptr;\n}\n\nvoid es_script::EngineContext::addImpulseResponse(\n    ImpulseResponseNode *node,\n    ImpulseResponse *impulse)\n{\n    m_impulseResponses[node] = impulse;\n}\n\nImpulseResponse *es_script::EngineContext::getImpulseResponse(\n    ImpulseResponseNode *node) const\n{\n    auto it = m_impulseResponses.find(node);\n    if (it != m_impulseResponses.end()) {\n        return it->second;\n    }\n    else return nullptr;\n}\n\nvoid es_script::EngineContext::addCrankshaft(\n    CrankshaftNode *node,\n    Crankshaft *crankshaft)\n{\n    m_crankshafts[node] = crankshaft;\n}\n\nCrankshaft *es_script::EngineContext::getCrankshaft(CrankshaftNode *node) const {\n    auto it = m_crankshafts.find(node);\n    if (it != m_crankshafts.end()) {\n        return it->second;\n    }\n    else return nullptr;\n}\n\nvoid es_script::EngineContext::addConnectingRod(\n    ConnectingRodNode *node,\n    ConnectingRod *rod)\n{\n    m_rods[node] = rod;\n}\n\nConnectingRod *es_script::EngineContext::getConnectingRod(\n    ConnectingRodNode *node) const\n{\n    auto it = m_rods.find(node);\n    if (it != m_rods.end()) {\n        return it->second;\n    }\n    else return nullptr;\n}\n\nvoid es_script::EngineContext::setCylinderIndex(\n    const CylinderBankNode *bank,\n    int localIndex,\n    int globalIndex)\n{\n    m_cylinderIndices[{ bank, localIndex }] = globalIndex;\n}\n\nint es_script::EngineContext::getCylinderIndex(\n    const CylinderBankNode *bank,\n    int localIndex) const\n{\n    return m_cylinderIndices.at({ bank, localIndex });\n}\n"
  },
  {
    "path": "scripting/src/language_rules.cpp",
    "content": "#include \"../include/language_rules.h\"\n\n#include \"../include/channel_types.h\"\n#include \"../include/engine_node.h\"\n#include \"../include/actions.h\"\n#include \"../include/rod_journal_node.h\"\n#include \"../include/camshaft_node.h\"\n#include \"../include/cylinder_head_node.h\"\n#include \"../include/ignition_module_node.h\"\n#include \"../include/impulse_response_node.h\"\n#include \"../include/standard_valvetrain_node.h\"\n#include \"../include/vtec_valvetrain_node.h\"\n#include \"../include/vehicle_node.h\"\n#include \"../include/transmission_node.h\"\n#include \"../include/throttle_nodes.h\"\n\nes_script::LanguageRules::LanguageRules() {\n    /* void */\n}\n\nes_script::LanguageRules::~LanguageRules() {\n    /* void */\n}\n\nvoid es_script::LanguageRules::registerBuiltinNodeTypes() {\n    // ====================================================\n    // Builtin types\n    // ====================================================\n\n    // Channels\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__float\", &piranha::FundamentalType::FloatType);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__int\", &piranha::FundamentalType::IntType);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__bool\", &piranha::FundamentalType::BoolType);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__string\", &piranha::FundamentalType::StringType);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__engine_channel\", &es_script::ObjectChannel::EngineChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__crankshaft_channel\", &es_script::ObjectChannel::CrankshaftChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__rod_journal_channel\", &es_script::ObjectChannel::RodJournalChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__connecting_rod_channel\", &es_script::ObjectChannel::ConnectingRodChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__piston_channel\", &es_script::ObjectChannel::PistonChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__cylinder_bank_channel\", &es_script::ObjectChannel::CylinderBankChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__function_channel\", &es_script::ObjectChannel::FunctionChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__cylinder_head_channel\", &es_script::ObjectChannel::CylinderHeadChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__camshaft_channel\", &es_script::ObjectChannel::CamshaftChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__intake_channel\", &es_script::ObjectChannel::IntakeChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__exhaust_system_channel\", &es_script::ObjectChannel::ExhaustSystemChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__ignition_module_channel\", &es_script::ObjectChannel::IgnitionModuleChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__ignition_wire_channel\", &es_script::ObjectChannel::IgnitionWireChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__fuel_channel\", &es_script::ObjectChannel::FuelChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__impulse_response_channel\", &es_script::ObjectChannel::ImpulseResponseChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__valvetrain_channel\", &es_script::ObjectChannel::ValvetrainChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__vehicle_channel\", &es_script::ObjectChannel::VehicleChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__transmission_channel\", &es_script::ObjectChannel::TransmissionChannel);\n    registerBuiltinType<piranha::ChannelNode>(\n        \"__engine_sim__throttle_channel\", &es_script::ObjectChannel::ThrottleChannel);\n\n    // Literals\n    registerBuiltinType<piranha::DefaultLiteralFloatNode>(\n        \"__engine_sim__literal_float\", &piranha::FundamentalType::FloatType);\n    registerBuiltinType<piranha::DefaultLiteralStringNode>(\n        \"__engine_sim__literal_string\", &piranha::FundamentalType::StringType);\n    registerBuiltinType<piranha::DefaultLiteralIntNode>(\n        \"__engine_sim__literal_int\", &piranha::FundamentalType::IntType);\n    registerBuiltinType<piranha::DefaultLiteralBoolNode>(\n        \"__engine_sim__literal_bool\", &piranha::FundamentalType::BoolType);\n\n    // Conversions\n    registerBuiltinType<piranha::IntToFloatConversionNode>(\n        \"__engine_sim__int_to_float\");\n    registerBuiltinType<piranha::IntToStringConversionNode>(\n        \"__engine_sim__int_to_string\");\n    registerBuiltinType<piranha::StringToIntConversionNode>(\n        \"__engine_sim__string_to_int\");\n\n    // Float operations\n    registerBuiltinType<piranha::NumNegateOperationNode<piranha::native_float>>(\n        \"__engine_sim__float_negate\");\n    registerBuiltinType<piranha::OperationNodeSpecialized<\n        piranha::native_float,\n        piranha::DivideOperationNodeOutput>>(\"__engine_sim__float_divide\");\n    registerBuiltinType<piranha::OperationNodeSpecialized<\n        piranha::native_float,\n        piranha::MultiplyOperationNodeOutput>>(\"__engine_sim__float_multiply\");\n    registerBuiltinType<piranha::OperationNodeSpecialized<\n        piranha::native_float,\n        piranha::DivideOperationNodeOutput>>(\"__engine_sim__float_divide\");\n    registerBuiltinType<piranha::OperationNodeSpecialized<\n        piranha::native_float,\n        piranha::AddOperationNodeOutput>>(\"__engine_sim__float_add\");\n    registerBuiltinType<piranha::OperationNodeSpecialized<\n        piranha::native_float,\n        piranha::SubtractOperationNodeOutput>>(\"__engine_sim__float_subtract\");\n\n    // Int operations\n    registerBuiltinType<piranha::OperationNodeSpecialized<\n        piranha::native_int,\n        piranha::MultiplyOperationNodeOutput>>(\"__engine_sim__int_multiply\");\n    registerBuiltinType<piranha::OperationNodeSpecialized<\n        piranha::native_int,\n        piranha::AddOperationNodeOutput>>(\"__engine_sim__int_add\");\n    registerBuiltinType<piranha::OperationNodeSpecialized<\n        piranha::native_int,\n        piranha::SubtractOperationNodeOutput>>(\"__engine_sim__int_subtract\");\n    registerBuiltinType<piranha::OperationNodeSpecialized<\n        piranha::native_int,\n        piranha::SubtractOperationNodeOutput>>(\"__engine_sim__int_divide\");\n    registerBuiltinType<piranha::NumNegateOperationNode<\n        piranha::native_int>>(\"__engine_sim__int_negate\");\n\n    // Actions\n    registerBuiltinType<SetEngineNode>(\"__engine_sim__set_engine\");\n    registerBuiltinType<AddRodJournalNode>(\"__engine_sim__add_rod_journal\");\n    registerBuiltinType<AddSlaveJournalNode>(\"__engine_sim__add_slave_journal\");\n    registerBuiltinType<AddCrankshaftNode>(\"__engine_sim__add_crankshaft\");\n    registerBuiltinType<AddCylinderBankNode>(\"__engine_sim__add_cylinder_bank\");\n    registerBuiltinType<AddCylinderNode>(\"__engine_sim__add_cylinder\");\n    registerBuiltinType<AddSampleNode>(\"__engine_sim__add_sample\");\n    registerBuiltinType<AddLobeNode>(\"__engine_sim__add_lobe\");\n    registerBuiltinType<SetCylinderHeadNode>(\"__engine_sim__set_cylinder_head\");\n    registerBuiltinType<ConnectIgnitionWireNode>(\"__engine_sim__connect_ignition_wire\");\n    registerBuiltinType<AddIgnitionModuleNode>(\"__engine_sim__add_ignition_module\");\n    registerBuiltinType<k_28inH2ONode>(\"__engine_sim__k_28inH2O\");\n    registerBuiltinType<k_CarbNode>(\"__engine_sim__k_carb\");\n    registerBuiltinType<GenerateHarmonicCamLobeNode>(\"__engine_sim__generate_harmonic_cam_lobe\");\n    registerBuiltinType<SetApplicationSettingsNode>(\"__engine_sim__set_application_settings\");\n    registerBuiltinType<SetVehicleNode>(\"__engine_sim__set_vehicle\");\n    registerBuiltinType<SetTransmissionNode>(\"__engine_sim__set_transmission\");\n    registerBuiltinType<AddGearNode>(\"__engine_sim__add_gear\");\n\n    // Objects\n    registerBuiltinType<EngineNode>(\"__engine_sim__engine\");\n    registerBuiltinType<RodJournalNode>(\"__engine_sim__rod_journal\");\n    registerBuiltinType<CrankshaftNode>(\"__engine_sim__crankshaft\");\n    registerBuiltinType<ConnectingRodNode>(\"__engine_sim__connecting_rod\");\n    registerBuiltinType<CylinderBankNode>(\"__engine_sim__cylinder_bank\");\n    registerBuiltinType<PistonNode>(\"__engine_sim__piston\");\n    registerBuiltinType<FunctionNode>(\"__engine_sim__function\");\n    registerBuiltinType<CylinderHeadNode>(\"__engine_sim__cylinder_head\");\n    registerBuiltinType<CamshaftNode>(\"__engine_sim__camshaft\");\n    registerBuiltinType<ExhaustSystemNode>(\"__engine_sim__exhaust_system\");\n    registerBuiltinType<IntakeNode>(\"__engine_sim__intake\");\n    registerBuiltinType<IgnitionModuleNode>(\"__engine_sim__ignition_module\");\n    registerBuiltinType<IgnitionWireNode>(\"__engine_sim__ignition_wire\");\n    registerBuiltinType<FuelNode>(\"__engine_sim__fuel\");\n    registerBuiltinType<ImpulseResponseNode>(\"__engine_sim__impulse_response\");\n    registerBuiltinType<StandardValvetrainNode>(\"__engine_sim__standard_valvetrain\");\n    registerBuiltinType<VtecValvetrainNode>(\"__engine_sim__vtec_valvetrain\");\n    registerBuiltinType<VehicleNode>(\"__engine_sim__vehicle\");\n    registerBuiltinType<TransmissionNode>(\"__engine_sim__transmission\");\n    registerBuiltinType<DirectThrottleLinkageNode>(\"__engine_sim__direct_throttle_linkage\");\n    registerBuiltinType<GovernorNode>(\"__engine_sim__governor\");\n\n    // String operations\n    registerBuiltinType<piranha::OperationNodeSpecialized<\n        piranha::native_string,\n        piranha::AddOperationNodeOutput>>(\"__engine_sim__string_add\");\n\n    // ====================================================\n    // Conversions\n    // ====================================================\n\n    registerConversion(\n        { &piranha::FundamentalType::IntType, &piranha::FundamentalType::FloatType },\n        \"__engine_sim__int_to_float\"\n    );\n    registerConversion(\n        { &piranha::FundamentalType::IntType, &piranha::FundamentalType::StringType },\n        \"__engine_sim__int_to_string\"\n    );\n    registerConversion(\n        { &piranha::FundamentalType::StringType, &piranha::FundamentalType::IntType },\n        \"__engine_sim__string_to_int\"\n    );\n\n    // Literals\n    registerLiteralType(piranha::LiteralType::Float, \"__engine_sim__literal_float\");\n    registerLiteralType(piranha::LiteralType::String, \"__engine_sim__literal_string\");\n    registerLiteralType(piranha::LiteralType::Integer, \"__engine_sim__literal_int\");\n    registerLiteralType(piranha::LiteralType::Boolean, \"__engine_sim__literal_bool\");\n\n    // Operations\n    registerUnaryOperator(\n        {\n            piranha::IrUnaryOperator::Operator::NumericNegate,\n            &piranha::FundamentalType::FloatType\n        },\n        \"__engine_sim__float_negate\");\n    registerOperator(\n        {\n            piranha::IrBinaryOperator::Operator::Mul,\n            &piranha::FundamentalType::FloatType,\n            &piranha::FundamentalType::FloatType\n        },\n        \"__engine_sim__float_multiply\");\n    registerOperator(\n        {\n            piranha::IrBinaryOperator::Operator::Mul,\n            &piranha::FundamentalType::FloatType,\n            &piranha::FundamentalType::IntType\n        },\n        \"__engine_sim__float_multiply\");\n    registerOperator(\n        {\n            piranha::IrBinaryOperator::Operator::Div,\n            &piranha::FundamentalType::FloatType,\n            &piranha::FundamentalType::FloatType\n        },\n        \"__engine_sim__float_divide\");\n    registerOperator(\n        {\n            piranha::IrBinaryOperator::Operator::Div,\n            &piranha::FundamentalType::FloatType,\n            &piranha::FundamentalType::IntType\n        },\n        \"__engine_sim__float_divide\");\n    registerOperator(\n        {\n            piranha::IrBinaryOperator::Operator::Sub,\n            &piranha::FundamentalType::FloatType,\n            &piranha::FundamentalType::FloatType\n        },\n        \"__engine_sim__float_subtract\");\n    registerOperator(\n        {\n            piranha::IrBinaryOperator::Operator::Sub,\n            &piranha::FundamentalType::FloatType,\n            &piranha::FundamentalType::IntType\n        },\n        \"__engine_sim__float_subtract\");\n    registerOperator(\n        {\n            piranha::IrBinaryOperator::Operator::Add,\n            &piranha::FundamentalType::FloatType,\n            &piranha::FundamentalType::FloatType\n        },\n        \"__engine_sim__float_add\");\n    registerOperator(\n        {\n            piranha::IrBinaryOperator::Operator::Add,\n            &piranha::FundamentalType::FloatType,\n            &piranha::FundamentalType::IntType\n        },\n        \"__engine_sim__float_add\");\n\n    registerUnaryOperator(\n        {\n            piranha::IrUnaryOperator::Operator::NumericNegate,\n            &piranha::FundamentalType::IntType\n        },\n        \"__engine_sim__int_negate\");\n    registerOperator(\n        {\n            piranha::IrBinaryOperator::Operator::Mul,\n            &piranha::FundamentalType::IntType,\n            &piranha::FundamentalType::IntType\n        },\n        \"__engine_sim__int_multiply\");\n    registerOperator(\n        {\n            piranha::IrBinaryOperator::Operator::Div,\n            &piranha::FundamentalType::IntType,\n            &piranha::FundamentalType::IntType\n        },\n        \"__engine_sim__int_divide\");\n    registerOperator(\n        {\n            piranha::IrBinaryOperator::Operator::Sub,\n            &piranha::FundamentalType::IntType,\n            &piranha::FundamentalType::IntType\n        },\n        \"__engine_sim__int_subtract\");\n    registerOperator(\n        {\n            piranha::IrBinaryOperator::Operator::Add,\n            &piranha::FundamentalType::IntType,\n            &piranha::FundamentalType::IntType\n        },\n        \"__engine_sim__int_add\");\n}\n"
  },
  {
    "path": "src/afr_cluster.cpp",
    "content": "#include \"../include/afr_cluster.h\"\n\n#include \"../include/units.h\"\n#include \"../include/gauge.h\"\n#include \"../include/constants.h\"\n#include \"../include/engine_sim_application.h\"\n\n#include <sstream>\n\nAfrCluster::AfrCluster() {\n    m_engine = nullptr;\n    m_intakeAfrGauge = nullptr;\n    m_exhaustAfrGauge = nullptr;\n}\n\nAfrCluster::~AfrCluster() {\n    /* void */\n}\n\nvoid AfrCluster::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n\n    m_intakeAfrGauge = addElement<LabeledGauge>();\n    m_exhaustAfrGauge = addElement<LabeledGauge>();\n\n    constexpr float shortenAngle = (float)units::angle(1.0, units::deg);\n\n    m_intakeAfrGauge->m_title = \"IN. AFR\";\n    m_intakeAfrGauge->m_unit = \"\";\n    m_intakeAfrGauge->m_precision = 1;\n    m_intakeAfrGauge->setLocalPosition({ 0, 0 });\n    m_intakeAfrGauge->m_gauge->m_min = 0;\n    m_intakeAfrGauge->m_gauge->m_max = 50;\n    m_intakeAfrGauge->m_gauge->m_minorStep = 1;\n    m_intakeAfrGauge->m_gauge->m_majorStep = 5;\n    m_intakeAfrGauge->m_gauge->m_maxMinorTick = 7000;\n    m_intakeAfrGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_intakeAfrGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_intakeAfrGauge->m_gauge->m_needleWidth = 4.0f;\n    m_intakeAfrGauge->m_gauge->m_gamma = 1.0f;\n    m_intakeAfrGauge->m_gauge->m_needleKs = 1000.0f;\n    m_intakeAfrGauge->m_gauge->m_needleKd = 20.0f;\n    m_intakeAfrGauge->m_gauge->setBandCount(0);\n\n    m_exhaustAfrGauge->m_title = \"EX. O2\";\n    m_exhaustAfrGauge->m_unit = \"\";\n    m_exhaustAfrGauge->m_precision = 1;\n    m_exhaustAfrGauge->setLocalPosition({ 0, 0 });\n    m_exhaustAfrGauge->m_gauge->m_min = 0;\n    m_exhaustAfrGauge->m_gauge->m_max = 100;\n    m_exhaustAfrGauge->m_gauge->m_minorStep = 5;\n    m_exhaustAfrGauge->m_gauge->m_majorStep = 10;\n    m_exhaustAfrGauge->m_gauge->m_maxMinorTick = 200;\n    m_exhaustAfrGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_exhaustAfrGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_exhaustAfrGauge->m_gauge->m_needleWidth = 4.0f;\n    m_exhaustAfrGauge->m_gauge->m_gamma = 1.0f;\n    m_exhaustAfrGauge->m_gauge->m_needleKs = 1000.0f;\n    m_exhaustAfrGauge->m_gauge->m_needleKd = 20.0f;\n    m_exhaustAfrGauge->m_gauge->setBandCount(0);\n}\n\nvoid AfrCluster::destroy() {\n    UiElement::destroy();\n}\n\nvoid AfrCluster::update(float dt) {\n    UiElement::update(dt);\n}\n\nvoid AfrCluster::render() {\n    const Bounds top = m_bounds.verticalSplit(0.5f, 1.0f);\n    const Bounds bottom = m_bounds.verticalSplit(0.0f, 0.5f);\n\n    m_intakeAfrGauge->m_bounds = top;\n    m_intakeAfrGauge->m_gauge->m_value = (m_engine != nullptr)\n        ? static_cast<float>(m_engine->getIntakeAfr())\n        : 0.0f;\n\n    m_exhaustAfrGauge->m_bounds = bottom;\n    m_exhaustAfrGauge->m_gauge->m_value = (m_engine != nullptr)\n        ? static_cast<float>(m_engine->getExhaustO2()) * 100.0f\n        : 0.0f;\n\n    UiElement::render();\n}\n"
  },
  {
    "path": "src/audio_buffer.cpp",
    "content": "#include \"../include/audio_buffer.h\"\n\n#include <assert.h>\n\nAudioBuffer::AudioBuffer() {\n    m_writePointer = 0;\n    m_sampleRate = 0;\n    m_samples = nullptr;\n    m_bufferSize = 0;\n    m_offsetToSeconds = 0;\n}\n\nAudioBuffer::~AudioBuffer() {\n    assert(m_samples == nullptr);\n}\n\nvoid AudioBuffer::initialize(int sampleRate, int bufferSize) {\n    m_writePointer = 0;\n    m_sampleRate = sampleRate;\n    m_samples = new int16_t[bufferSize];\n    memset(m_samples, 0, sizeof(int16_t) * bufferSize);\n    m_bufferSize = bufferSize;\n    m_offsetToSeconds = 1 / (double)sampleRate;\n}\n\nvoid AudioBuffer::destroy() {\n    delete[] m_samples;\n\n    m_samples = nullptr;\n    m_bufferSize = 0;\n}\n\nbool AudioBuffer::checkForDiscontinuitiy(int threshold) const {\n    for (int i = 0; i < m_bufferSize - 1; ++i) {\n        const int i0 = getBufferIndex(i + m_writePointer);\n        const int i1 = getBufferIndex(i0 + 1);\n\n        if (std::abs(m_samples[i0] - m_samples[i1]) >= threshold) {\n            return true;\n        }\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/camshaft.cpp",
    "content": "#include \"../include/camshaft.h\"\n\n#include \"../include/crankshaft.h\"\n#include \"../include/constants.h\"\n#include \"../include/units.h\"\n\n#include <cmath>\n#include <assert.h>\n\nCamshaft::Camshaft() {\n    m_crankshaft = nullptr;\n    m_lobeAngles = nullptr;\n    m_lobeProfile = nullptr;\n    m_lobes = 0;\n    m_advance = 0;\n    m_baseRadius = 0;\n}\n\nCamshaft::~Camshaft() {\n    assert(m_lobeAngles == nullptr);\n}\n\nvoid Camshaft::initialize(const Parameters &params) {\n    m_lobeAngles = new double[params.lobes];\n    memset(m_lobeAngles, 0, sizeof(double) * params.lobes);\n\n    m_lobes = params.lobes;\n    m_crankshaft = params.crankshaft;\n    m_lobeProfile = params.lobeProfile;\n    m_advance = params.advance;\n    m_baseRadius = params.baseRadius;\n}\n\nvoid Camshaft::destroy() {\n    delete[] m_lobeAngles;\n    m_lobeAngles = nullptr;\n\n    m_lobes = 0;\n}\n\ndouble Camshaft::valveLift(int lobe) const {\n    return sampleLobe(getAngle() + m_lobeAngles[lobe]);\n}\n\ndouble Camshaft::sampleLobe(double theta) const {\n    double clampedTheta = std::fmod(theta, 2 * constants::pi);\n    if (clampedTheta < 0) clampedTheta += 2 * constants::pi;\n    if (clampedTheta >= constants::pi) clampedTheta -= 2 * constants::pi;\n\n    return m_lobeProfile->sampleTriangle(clampedTheta);\n}\n\ndouble Camshaft::getAngle() const {\n    const double angle =\n        std::fmod((m_crankshaft->getAngle() + m_advance) * 0.5, 2 * constants::pi);\n    return (angle < 0)\n        ?  angle + 2 * constants::pi\n        :  angle;\n}\n"
  },
  {
    "path": "src/combustion_chamber.cpp",
    "content": "#include \"../include/combustion_chamber.h\"\n\n#include \"../include/constants.h\"\n#include \"../include/units.h\"\n#include \"../include/piston.h\"\n#include \"../include/connecting_rod.h\"\n#include \"../include/utilities.h\"\n#include \"../include/exhaust_system.h\"\n#include \"../include/cylinder_bank.h\"\n#include \"../include/engine.h\"\n\n#include <cmath>\n\nCombustionChamber::CombustionChamber() {\n    m_crankcasePressure = 0.0;\n    m_piston = nullptr;\n    m_head = nullptr;\n    m_engine = nullptr;\n    m_pistonSpeed = nullptr;\n    m_pressure = nullptr;\n    m_lit = false;\n    m_litLastFrame = false;\n    m_peakTemperature = 0;\n\n    m_meanPistonSpeedToTurbulence = nullptr;\n    m_nBurntFuel = 0;\n\n    m_manifoldToRunnerFlowRate = 0;\n    m_primaryToCollectorFlowRate = 0;\n    m_cylinderWidthApproximation = 0;\n    m_cylinderCrossSectionSurfaceArea = 0;\n\n    m_lastTimestepTotalExhaustFlow = 0;\n    m_lastTimestepTotalIntakeFlow = 0;\n    m_exhaustFlow = 0;\n    m_exhaustFlowRate = 0;\n    m_intakeFlowRate = 0;\n\n    m_fuel = nullptr;\n}\n\nCombustionChamber::~CombustionChamber() {\n    assert(m_pistonSpeed == nullptr);\n    assert(m_pressure == nullptr);\n}\n\nvoid CombustionChamber::initialize(const Parameters &params) {\n    m_piston = params.Piston;\n    m_head = params.Head;\n    m_fuel = params.Fuel;\n    m_crankcasePressure = params.CrankcasePressure;\n    m_meanPistonSpeedToTurbulence = params.MeanPistonSpeedToTurbulence;\n\n    m_pistonSpeed = new double[StateSamples];\n    m_pressure = new double[StateSamples];\n    for (int i = 0; i < StateSamples; ++i) {\n        m_pistonSpeed[i] = 0;\n        m_pressure[i] = 0;\n    }\n\n    Intake *intake = m_head->getIntake(m_piston->getCylinderIndex());\n    ExhaustSystem *exhaust = m_head->getExhaustSystem(m_piston->getCylinderIndex());\n\n    m_manifoldToRunnerFlowRate = intake->getRunnerFlowRate();\n    m_primaryToCollectorFlowRate = exhaust->getPrimaryFlowRate();\n\n    const double bore_r = m_head->getCylinderBank()->getBore() / 2.0;\n    m_cylinderCrossSectionSurfaceArea = constants::pi * bore_r * bore_r;\n    m_cylinderWidthApproximation = std::sqrt(m_cylinderCrossSectionSurfaceArea);\n\n    const double height = getVolume() / m_cylinderCrossSectionSurfaceArea;\n    m_system.setGeometry(\n        m_cylinderWidthApproximation,\n        height,\n        1.0,\n        0.0);\n\n    const double intakeRunnerCrossSection = m_head->getIntakeRunnerCrossSectionArea();\n    const double intakeRunnerWidth = std::sqrt(intakeRunnerCrossSection);\n    const double manifoldRunnerLength = intake->getRunnerLength();\n    const double manifoldRunnerVolume = intakeRunnerCrossSection * manifoldRunnerLength;\n    const double totalIntakeRunnerVolume = m_head->getIntakeRunnerVolume() + manifoldRunnerVolume;\n    const double overallIntakeRunnerLength = totalIntakeRunnerVolume / intakeRunnerCrossSection;\n    m_intakeRunnerAndManifold.initialize(\n        units::pressure(1.0, units::atm),\n        totalIntakeRunnerVolume,\n        units::celcius(25.0));\n    m_intakeRunnerAndManifold.setGeometry(\n        overallIntakeRunnerLength,\n        intakeRunnerWidth,\n        1.0,\n        0.0);\n\n    const double exhaustRunnerCrossSection = m_head->getExhaustRunnerCrossSectionArea();\n    const double exhaustRunnerWidth = std::sqrt(exhaustRunnerCrossSection);\n    const double exhaustTubeLength =\n        exhaust->getPrimaryTubeLength() + m_head->getHeaderPrimaryLength(m_piston->getCylinderIndex());\n    const double exhaustTubeVolume = exhaustRunnerCrossSection * exhaustTubeLength;\n    const double totalExhaustRunnerVolume = m_head->getExhaustRunnerVolume() + exhaustTubeVolume;\n    const double overallExhaustRunnerLength = totalExhaustRunnerVolume / exhaustRunnerCrossSection;\n    m_exhaustRunnerAndPrimary.initialize(\n        units::pressure(1.0, units::atm),\n        totalExhaustRunnerVolume,\n        units::celcius(25.0));\n    m_exhaustRunnerAndPrimary.setGeometry(\n        overallExhaustRunnerLength,\n        exhaustRunnerWidth,\n        1.0,\n        0.0);\n}\n\nvoid CombustionChamber::destroy() {\n    if (m_pistonSpeed != nullptr) delete[] m_pistonSpeed;\n    if (m_pressure != nullptr) delete[] m_pressure;\n\n    m_pistonSpeed = nullptr;\n    m_pressure = nullptr;\n}\n\ndouble CombustionChamber::getVolume() const {\n    const double combustionPortVolume = m_head->getCombustionChamberVolume();\n    const CylinderBank *bank = m_head->getCylinderBank();\n\n    const double area = bank->boreSurfaceArea();\n    const double s =\n        m_piston->relativeX() * bank->getDx()\n        + m_piston->relativeY() * bank->getDy();\n    const double sweep =\n        area * (bank->getDeckHeight() - s - m_piston->getCompressionHeight());\n\n    return sweep + combustionPortVolume - m_piston->getDisplacement();\n}\n\ndouble CombustionChamber::pistonSpeed() const {\n    const CylinderBank *bank = m_head->getCylinderBank();\n    return\n        m_piston->m_body.v_x * bank->getDx()\n        + m_piston->m_body.v_y * bank->getDy();\n}\n\ndouble CombustionChamber::calculateMeanPistonSpeed() const {\n    double avg = 0;\n    for (int i = 0; i < StateSamples; ++i) {\n        avg += m_pistonSpeed[i];\n    }\n\n    avg /= StateSamples;\n    return avg;\n}\n\ndouble CombustionChamber::calculateFiringPressure() const {\n    double firingPressure = 0;\n    for (int i = 0; i < StateSamples; ++i) {\n        if (m_pressure[i] > firingPressure) {\n            firingPressure = m_pressure[i];\n        }\n    }\n\n    return firingPressure;\n}\n\nbool CombustionChamber::popLitLastFrame() {\n    const bool lit = m_litLastFrame;\n    m_litLastFrame = false;\n\n    return lit;\n}\n\nvoid CombustionChamber::ignite() {\n    if (!m_lit) {\n        if (m_system.mix().p_fuel == 0) return;\n\n        const double afr = m_system.mix().p_o2 / m_system.mix().p_fuel;\n        const double equivalenceRatio = afr / m_fuel->getMolecularAfr();\n        if (equivalenceRatio < 0.5) return;\n        else if (equivalenceRatio > 1.9) return;\n\n        const double idealInert = m_system.mix().p_o2 / 0.7;\n        const double dilution = (m_system.mix().p_inert / idealInert) - 1;\n\n        m_flameEvent.lastVolume = getVolume();\n        m_flameEvent.travel_x = 0;\n        m_flameEvent.travel_y = 0;\n        m_flameEvent.lit_n = 0;\n        m_flameEvent.total_n = m_system.n();\n        m_flameEvent.percentageLit = 0;\n        m_flameEvent.globalMix = m_system.mix();\n        m_lit = true;\n        m_litLastFrame = true;\n\n        const double randomness =\n            m_fuel->getBurningEfficiencyRandomness();\n        const double lowEfficiencyAttenuation =\n            m_fuel->getLowEfficiencyAttenuation();\n        const double maxBurningEfficiency =\n            m_fuel->getMaxBurningEfficiency();\n        const double maxTurbulenceEffect =\n            m_fuel->getMaxTurbulenceEffect();\n        const double maxDilutionEffect =\n            m_fuel->getMaxDilutionEffect();\n\n        const double turbulence =\n            m_meanPistonSpeedToTurbulence->sampleTriangle(\n                calculateMeanPistonSpeed());\n        const double mixingFactor =\n            1.0 - (\n                clamp(turbulence / maxTurbulenceEffect)\n                * clamp(1 - dilution / maxDilutionEffect));\n        const double rand_s =\n            lowEfficiencyAttenuation\n            * ((1 - randomness) + randomness * ((double)rand() / RAND_MAX));\n        const double efficiencyAttenuation =\n            (mixingFactor * rand_s + (1 - mixingFactor));\n        m_flameEvent.efficiency =\n            efficiencyAttenuation * maxBurningEfficiency;\n        m_flameEvent.flameSpeed = m_fuel->flameSpeed(\n            turbulence,\n            afr,\n            m_system.temperature(),\n            m_system.pressure(),\n            calculateFiringPressure(),\n            units::pressure(160, units::psi));\n    }\n}\n\nvoid CombustionChamber::update(double dt) {\n    m_system.setVolume(getVolume());\n\n    updateCycleStates();\n\n    m_intakeFlowRate = m_head->intakeFlowRate(m_piston->getCylinderIndex());\n    m_exhaustFlowRate = m_head->exhaustFlowRate(m_piston->getCylinderIndex());\n}\n\nvoid CombustionChamber::flow(double dt) {\n    if (m_system.temperature() > m_peakTemperature) {\n        m_peakTemperature = m_system.temperature();\n    }\n\n    const double volume = getVolume();\n    const double cylinderHeight = volume / m_cylinderCrossSectionSurfaceArea;\n    const double cylinderSurfaceArea =\n        cylinderHeight * constants::pi * m_head->getCylinderBank()->getBore()\n        + m_cylinderCrossSectionSurfaceArea * 2;\n\n    const double dT = units::celcius(90.0) - m_system.temperature();\n\n    m_system.changeEnergy(dT * cylinderSurfaceArea * 100 * dt);\n    m_system.flow(m_piston->getBlowbyK(), dt, m_crankcasePressure, units::celcius(25.0));\n\n    Intake *intake = m_head->getIntake(m_piston->getCylinderIndex());\n    ExhaustSystem *exhaust = m_head->getExhaustSystem(m_piston->getCylinderIndex());\n\n    const double start_n = m_system.n();\n\n    GasSystem::FlowParameters flowParams;\n    flowParams.dt = dt;\n\n    flowParams.k_flow = m_manifoldToRunnerFlowRate;\n    flowParams.crossSectionArea_0 = intake->getPlenumCrossSectionArea();\n    flowParams.crossSectionArea_1 = m_head->getIntakeRunnerCrossSectionArea();\n    flowParams.direction_x = 1.0;\n    flowParams.direction_y = 0.0;\n    flowParams.system_0 = &intake->m_system;\n    flowParams.system_1 = &m_intakeRunnerAndManifold;\n    GasSystem::flow(flowParams);\n\n    m_intakeRunnerAndManifold.dissipateExcessVelocity();\n\n    flowParams.k_flow = m_intakeFlowRate;\n    flowParams.crossSectionArea_0 = m_head->getIntakeRunnerCrossSectionArea();\n    flowParams.crossSectionArea_1 = volume / cylinderHeight;\n    flowParams.direction_x = 1.0;\n    flowParams.direction_y = 0.0;\n    flowParams.system_0 = &m_intakeRunnerAndManifold;\n    flowParams.system_1 = &m_system;\n    const double intakeFlow = GasSystem::flow(flowParams);\n\n    m_intakeRunnerAndManifold.dissipateExcessVelocity();\n    m_system.dissipateExcessVelocity();\n\n    flowParams.k_flow = m_exhaustFlowRate;\n    flowParams.crossSectionArea_0 = volume / cylinderHeight;\n    flowParams.crossSectionArea_1 = m_head->getExhaustRunnerCrossSectionArea();\n    flowParams.direction_x = 1.0;\n    flowParams.direction_y = 0.0;\n    flowParams.system_0 = &m_system;\n    flowParams.system_1 = &m_exhaustRunnerAndPrimary;\n    const double exhaustFlow = GasSystem::flow(flowParams);\n\n    m_system.dissipateExcessVelocity();\n    m_exhaustRunnerAndPrimary.dissipateExcessVelocity();\n\n    flowParams.k_flow = m_primaryToCollectorFlowRate;\n    flowParams.crossSectionArea_0 = m_head->getExhaustRunnerCrossSectionArea();\n    flowParams.crossSectionArea_1 = exhaust->getCollectorCrossSectionArea();\n    flowParams.direction_x = 1.0;\n    flowParams.direction_y = 0.0;\n    flowParams.system_0 = &m_exhaustRunnerAndPrimary;\n    flowParams.system_1 = exhaust->getSystem();\n    GasSystem::flow(flowParams);\n\n    m_intakeRunnerAndManifold.updateVelocity(dt, intake->getVelocityDecay());\n    m_system.updateVelocity(dt, 0.5);\n    m_exhaustRunnerAndPrimary.updateVelocity(dt, exhaust->getVelocityDecay());\n\n    if (std::abs(intakeFlow) > 1E-9 && m_lit) {\n        m_lit = false;\n    }\n\n    m_exhaustFlow = exhaustFlow;\n    m_lastTimestepTotalExhaustFlow += exhaustFlow;\n    m_lastTimestepTotalIntakeFlow += intakeFlow;\n\n    if (m_lit) {\n        CylinderBank *bank = m_head->getCylinderBank();\n        const double totalTravel_x = bank->getBore() / 2;\n        const double totalTravel_y = volume / bank->boreSurfaceArea();\n        const double expansion = volume / m_flameEvent.lastVolume;\n        const double lastTravel_x = m_flameEvent.travel_x;\n        const double lastTravel_y = m_flameEvent.travel_y * expansion;\n        const double flameSpeed = m_flameEvent.flameSpeed;\n\n        m_flameEvent.travel_x =\n            std::fmin(lastTravel_x + dt * flameSpeed, totalTravel_x);\n        m_flameEvent.travel_y =\n            std::fmin(lastTravel_y + dt * flameSpeed, totalTravel_y);\n\n        if (lastTravel_x < m_flameEvent.travel_x || lastTravel_y < m_flameEvent.travel_y) {\n            const double burnedVolume =\n                m_flameEvent.travel_x * m_flameEvent.travel_x\n                * constants::pi * m_flameEvent.travel_y;\n            const double prevBurnedVolume =\n                lastTravel_x * lastTravel_x * constants::pi * lastTravel_y;\n            const double litVolume = burnedVolume - prevBurnedVolume;\n            const double n = (litVolume / volume) * m_system.n();\n\n            const double fuelBurned =\n                m_system.react(n * m_flameEvent.efficiency, m_flameEvent.globalMix);\n            const double massFuelBurned = fuelBurned * m_fuel->getMolecularMass();\n            m_system.changeEnergy(\n                massFuelBurned * m_fuel->getEnergyDensity());\n\n            m_flameEvent.lit_n += n;\n            m_flameEvent.percentageLit += litVolume / volume;\n\n            m_nBurntFuel += massFuelBurned;\n        }\n        else {\n            m_lit = false;\n        }\n\n        m_flameEvent.lastVolume = volume;\n    }\n}\n\ndouble CombustionChamber::lastEventAfr() const {\n    const double totalFuel = m_flameEvent.globalMix.p_fuel * m_flameEvent.total_n;\n    const double totalOxygen = m_flameEvent.globalMix.p_o2 * m_flameEvent.total_n;\n    const double totalInert = m_flameEvent.globalMix.p_inert * m_flameEvent.total_n;\n\n    constexpr double octaneMolarMass = units::mass(114.23, units::g);\n    constexpr double oxygenMolarMass = units::mass(31.9988, units::g);\n    constexpr double nitrogenMolarMass = units::mass(28.014, units::g);\n\n    if (totalFuel == 0) return 0;\n    else {\n        return\n            (oxygenMolarMass * totalOxygen + totalInert * nitrogenMolarMass)\n            / (totalFuel * octaneMolarMass);\n    }\n}\n\ndouble CombustionChamber::calculateFrictionForce(double v_s) const {\n    const double cylinderWallForce = m_piston->calculateCylinderWallForce();\n\n    const double F_coul = m_frictionModel.frictionCoeff * cylinderWallForce;\n    const double v_st = m_frictionModel.breakawayFrictionVelocity * constants::root_2;\n    const double v_coul = m_frictionModel.breakawayFrictionVelocity / 10;\n    const double F_brk = m_frictionModel.breakawayFriction;\n    const double v = std::abs(v_s);\n\n    const double F_0 = constants::root_2 * constants::e * (F_brk - F_coul);\n    const double F_1 = v / v_st;\n    const double F_2 = std::exp(-F_1 * F_1) * F_1;\n    const double F_3 = F_coul * std::tanh(v / v_coul);\n    const double F_4 = m_frictionModel.viscousFrictionCoefficient * v;\n\n    return F_0 * F_2 + F_3 + F_4;\n}\n\nvoid CombustionChamber::updateCycleStates() {\n    double crankAngle = m_engine->getOutputCrankshaft()->getCycleAngle();\n    if (std::isnan(crankAngle) || std::isinf(crankAngle)) {\n        crankAngle = 0.0;\n    }\n\n    const int i = (int)std::round((crankAngle / (4 * constants::pi)) * (StateSamples - 1.0));\n\n    m_pistonSpeed[i] = std::abs(pistonSpeed());\n    m_pressure[i] = m_system.pressure();\n}\n\nvoid CombustionChamber::apply(atg_scs::SystemState *system) {\n    CylinderBank *bank = m_head->getCylinderBank();\n    const double area = (bank->getBore() * bank->getBore() / 4.0) * constants::pi;\n    const double v_x = system->v_x[m_piston->m_body.index];\n    const double v_y = system->v_y[m_piston->m_body.index];\n\n    const double v_s =\n        v_x * bank->getDx() + v_y * bank->getDy();\n\n    const double pressureDifferential = m_system.pressure() - m_crankcasePressure;\n    const double force = -area * pressureDifferential;\n\n    if (std::isnan(force) || std::isinf(force)) {\n        assert(false);\n    }\n\n    constexpr double limit = 1E-3;\n    const double abs_v_s = std::fmin(std::abs(v_s), limit);\n    const double attenuation = abs_v_s / limit;\n\n    const double F = calculateFrictionForce(v_s) * attenuation;\n    const double F_fric = (v_s > 0)\n        ? -F\n        : F;\n\n    system->applyForce(\n        0.0,\n        0.0,\n        (force + F_fric) * bank->getDx(),\n        (force + F_fric) * bank->getDy(),\n        m_piston->m_body.index);\n}\n\ndouble CombustionChamber::getFrictionForce() const {\n    CylinderBank *bank = m_head->getCylinderBank();\n    const double v_x = m_piston->m_body.v_x;\n    const double v_y = m_piston->m_body.v_y;\n\n    const double v_s =\n        v_x * bank->getDx() + v_y * bank->getDy();\n\n    return calculateFrictionForce(v_s);\n}\n"
  },
  {
    "path": "src/combustion_chamber_object.cpp",
    "content": "#include \"../include/combustion_chamber_object.h\"\n\n#include \"../include/cylinder_bank.h\"\n#include \"../include/engine_sim_application.h\"\n#include \"../include/constants.h\"\n\nCombustionChamberObject::CombustionChamberObject() {\n    m_chamber = nullptr;\n}\n\nCombustionChamberObject::~CombustionChamberObject() {\n    /* void */\n}\n\nvoid CombustionChamberObject::generateGeometry() {\n    GeometryGenerator *gen = m_app->getGeometryGenerator();\n    CylinderHead *head = m_chamber->getCylinderHead();\n    CylinderBank *bank = head->getCylinderBank();\n\n    const float lineWidth = (float)m_chamber->m_flameEvent.travel_x * 2;\n    double flameTop_x, flameTop_y;\n    double flameBottom_x, flameBottom_y;\n    double chamberHeight = head->getCombustionChamberVolume() / bank->boreSurfaceArea();\n\n    bank->getPositionAboveDeck(chamberHeight, &flameTop_x, &flameTop_y);\n    bank->getPositionAboveDeck(chamberHeight - m_chamber->m_flameEvent.travel_y, &flameBottom_x, &flameBottom_y);\n\n    GeometryGenerator::Line2dParameters params;\n    params.lineWidth = lineWidth;\n\n    gen->startShape();\n\n    params.x0 = (float)flameTop_x;\n    params.y0 = (float)flameTop_y;\n    params.x1 = (float)flameBottom_x;\n    params.y1 = (float)flameBottom_y;\n    gen->generateLine2d(params);\n\n    gen->endShape(&m_indices);\n}\n\nvoid CombustionChamberObject::render(const ViewParameters *view) {\n    resetShader();\n\n    CylinderHead *head = m_chamber->getCylinderHead();\n    CylinderBank *bank = head->getCylinderBank();\n\n    Piston *frontmostPiston = getForemostPiston(bank, view->Layer0);\n    if (m_chamber->getPiston() == frontmostPiston) {\n        if (m_chamber->m_lit) {\n            m_app->getShaders()->SetBaseColor(\n                ysMath::Mul(\n                    m_app->getOrange(),\n                    ysMath::LoadVector(1.0f, 1.0f, 1.0f, 0.6f)));\n            m_app->drawGenerated(m_indices, 0x35);\n        }\n    }\n}\n\nvoid CombustionChamberObject::process(float dt) {\n    /* void */\n}\n\nvoid CombustionChamberObject::destroy() {\n    /* void */\n}\n"
  },
  {
    "path": "src/connecting_rod.cpp",
    "content": "#include \"..\\include\\connecting_rod.h\"\n#include \"../include/connecting_rod.h\"\n\n#include <cmath>\n\nConnectingRod::ConnectingRod() {\n    m_centerOfMass = 0.0;\n    m_length = 0.0;\n    m_m = 0.0;\n    m_I = 0.0;\n    m_journal = 0;\n    m_master = nullptr;\n    m_crankshaft = nullptr;\n    m_piston = nullptr;\n    m_slaveThrow = 0;\n\n    m_rodJournalAngles = nullptr;\n    m_rodJournalCount = 0;\n}\n\nConnectingRod::~ConnectingRod() {\n    /* void */\n}\n\nvoid ConnectingRod::initialize(const Parameters &params) {\n    m_centerOfMass = params.centerOfMass;\n    m_length = params.length;\n    m_m = params.mass;\n    m_I = params.momentOfInertia;\n    m_journal = params.journal;\n    m_crankshaft = params.crankshaft;\n    m_piston = params.piston;\n\n    m_rodJournalAngles = new double[params.rodJournals];\n    m_rodJournalCount = params.rodJournals;\n    m_slaveThrow = params.slaveThrow;\n    m_master = params.master;\n}\n\ndouble ConnectingRod::getBigEndLocal() const {\n    return -(m_length / 2) + m_centerOfMass;\n}\n\ndouble ConnectingRod::getLittleEndLocal() const {\n    return (m_length / 2) - m_centerOfMass;\n}\n\nvoid ConnectingRod::setRodJournalAngle(int i, double angle) {\n    m_rodJournalAngles[i] = angle;\n}\n\nvoid ConnectingRod::getRodJournalPositionLocal(int i, double *x, double *y) {\n    const double journalAngle = getRodJournalAngle(i);\n    const double journal_x_local = std::cos(journalAngle) * m_slaveThrow;\n    const double journal_y_local = std::sin(journalAngle) * m_slaveThrow;\n\n    *x = journal_x_local;\n    *y = journal_y_local + getBigEndLocal();\n}\n\nvoid ConnectingRod::getRodJournalPositionGlobal(int i, double *x, double *y) {\n    double lx, ly;\n    getRodJournalPositionLocal(i, &lx, &ly);\n\n    const double angle = m_body.theta;\n    const double dx = std::cos(angle);\n    const double dy = std::sin(angle);\n\n    *x = (dx * lx - dy * ly) + m_body.p_x;\n    *y = (dy * lx + dx * ly) + m_body.p_y;\n}\n\nint ConnectingRod::getLayer() const {\n    if (m_master != nullptr) {\n        return m_master->getLayer();\n    }\n    else {\n        return getJournal();\n    }\n}\n"
  },
  {
    "path": "src/connecting_rod_object.cpp",
    "content": "#include \"../include/connecting_rod_object.h\"\n\n#include \"../include/engine_sim_application.h\"\n#include \"../include/units.h\"\n#include \"../include/ui_utilities.h\"\n\nConnectingRodObject::ConnectingRodObject() {\n    m_connectingRod = nullptr;\n}\n\nConnectingRodObject::~ConnectingRodObject() {\n    /* void */\n}\n\nvoid ConnectingRodObject::generateGeometry() {\n    GeometryGenerator *gen = m_app->getGeometryGenerator();\n    const int rodJournalCount = m_connectingRod->getRodJournalCount();\n\n    GeometryGenerator::Line2dParameters params;\n    params.x0 = params.x1 = 0;\n    params.y0 = (float)(m_connectingRod->getBigEndLocal() + m_connectingRod->getCrankshaft()->getThrow() * 0.6);\n    params.y1 = (float)m_connectingRod->getLittleEndLocal();\n    params.lineWidth = (float)(m_connectingRod->getCrankshaft()->getThrow() * 0.5);\n\n    gen->startShape();\n    gen->generateLine2d(params);\n\n    if (rodJournalCount > 0) {\n        GeometryGenerator::Circle2dParameters circleParams;\n        circleParams.radius = static_cast<float>(m_connectingRod->getSlaveThrow()) * 1.5f;\n        circleParams.center_x = 0.0f;\n        circleParams.center_y = static_cast<float>(m_connectingRod->getBigEndLocal());\n\n        gen->generateCircle2d(circleParams);\n    }\n\n    gen->endShape(&m_connectingRodBody);\n\n    if (rodJournalCount > 0) {\n        gen->startShape();\n\n        GeometryGenerator::Circle2dParameters circleParams;\n        circleParams.radius = static_cast<float>(m_connectingRod->getCrankshaft()->getThrow()) * 0.2f;\n        for (int i = 0; i < rodJournalCount; ++i) {\n            double x, y;\n            m_connectingRod->getRodJournalPositionLocal(i, &x, &y);\n\n            circleParams.center_x = static_cast<float>(x);\n            circleParams.center_y = static_cast<float>(y);\n\n            gen->generateCircle2d(circleParams);\n        }\n\n        gen->endShape(&m_pins);\n    }\n}\n\nvoid ConnectingRodObject::render(const ViewParameters *view) {\n    if (m_connectingRod->getRodJournalCount() > 0 && view->Sublayer != 1) return;\n    else if (m_connectingRod->getRodJournalCount() == 0 && view->Sublayer != 0) return;\n\n    const int layer = m_connectingRod->getLayer();\n    if (layer > view->Layer1 || layer < view->Layer0) return;\n\n    const ysVector grey0 = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.9333f);\n    const ysVector grey1 = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.8667f);\n    const ysVector grey2 = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.05f);\n\n    ysVector color =\n        (m_connectingRod->getPiston()->getCylinderBank()->getIndex() % 2 == 0)\n        ? grey0\n        : grey1;\n    color = tintByLayer(color, layer - view->Layer0);\n\n    resetShader();\n    setTransform(\n        &m_connectingRod->m_body,\n        (float)m_connectingRod->getCrankshaft()->getThrow(),\n        0.0f,\n        (float)m_connectingRod->getBigEndLocal());\n\n    m_app->getShaders()->SetBaseColor(color);\n    m_app->getEngine()->DrawModel(\n        m_app->getShaders()->GetRegularFlags(),\n        m_app->getAssetManager()->GetModelAsset(\"ConnectingRod\"),\n        0x32 - layer);\n\n    m_app->getShaders()->SetBaseColor(color);\n    setTransform(&m_connectingRod->m_body);\n    m_app->drawGenerated(m_connectingRodBody, 0x32 - layer);\n\n    if (m_connectingRod->getRodJournalCount() > 0) {\n        const ysVector shadow =\n            tintByLayer(grey2, layer - view->Layer0);\n\n        m_app->getShaders()->SetBaseColor(shadow);\n        m_app->drawGenerated(m_pins, 0x32 - layer);\n    }\n}\n\nvoid ConnectingRodObject::process(float dt) {\n    /* void */\n}\n\nvoid ConnectingRodObject::destroy() {\n    /* void */\n}\n"
  },
  {
    "path": "src/convolution_filter.cpp",
    "content": "#include \"../include/convolution_filter.h\"\n\n#include <assert.h>\n#include <string.h>\n\nConvolutionFilter::ConvolutionFilter() {\n    m_shiftRegister = nullptr;\n    m_impulseResponse = nullptr;\n\n    m_shiftOffset = 0;\n    m_sampleCount = 0;\n}\n\nConvolutionFilter::~ConvolutionFilter() {\n    assert(m_shiftRegister == nullptr);\n    assert(m_impulseResponse == nullptr);\n}\n\nvoid ConvolutionFilter::initialize(int samples) {\n    m_sampleCount = samples;\n    m_shiftOffset = 0;\n    m_shiftRegister = new float[samples];\n    m_impulseResponse = new float[samples];\n\n    memset(m_shiftRegister, 0, sizeof(float) * samples);\n    memset(m_impulseResponse, 0, sizeof(float) * samples);\n}\n\nvoid ConvolutionFilter::destroy() {\n    delete[] m_shiftRegister;\n    delete[] m_impulseResponse;\n\n    m_shiftRegister = nullptr;\n    m_impulseResponse = nullptr;\n}\n\nfloat ConvolutionFilter::f(float sample) {\n    m_shiftRegister[m_shiftOffset] = sample;\n\n    float result = 0;\n    for (int i = 0; i < m_sampleCount - m_shiftOffset; ++i) {\n        result += m_impulseResponse[i] * m_shiftRegister[i + m_shiftOffset];\n    }\n\n    for (int i = m_sampleCount - m_shiftOffset; i < m_sampleCount; ++i) {\n        result += m_impulseResponse[i] * m_shiftRegister[i - (m_sampleCount - m_shiftOffset)];\n    }\n\n    m_shiftOffset = (m_shiftOffset - 1 + m_sampleCount) % m_sampleCount;\n\n    return result;\n}\n"
  },
  {
    "path": "src/crankshaft.cpp",
    "content": "#include \"../include/crankshaft.h\"\n\n#include \"../include/constants.h\"\n\n#include <cmath>\n#include <assert.h>\n\nCrankshaft::Crankshaft() {\n    m_rodJournalAngles = nullptr;\n    m_rodJournalCount = 0;\n    m_throw = 0.0;\n    m_m = 0.0;\n    m_I = 0.0;\n    m_flywheelMass = 0.0;\n    m_p_x = m_p_y = 0.0;\n    m_tdc = 0.0;\n    m_frictionTorque = 0.0;\n}\n\nCrankshaft::~Crankshaft() {\n    assert(m_rodJournalAngles == nullptr);\n}\n\nvoid Crankshaft::initialize(const Parameters &params) {\n    m_m = params.mass;\n    m_flywheelMass = params.flywheelMass;\n    m_I = params.momentOfInertia;\n    m_throw = params.crankThrow;\n    m_rodJournalCount = params.rodJournals;\n    m_rodJournalAngles = new double[m_rodJournalCount];\n    m_p_x = params.pos_x;\n    m_p_y = params.pos_y;\n    m_tdc = params.tdc;\n    m_frictionTorque = params.frictionTorque;\n}\n\nvoid Crankshaft::destroy() {\n    if (m_rodJournalAngles != nullptr) delete[] m_rodJournalAngles;\n\n    m_rodJournalAngles = nullptr;\n}\n\nvoid Crankshaft::getRodJournalPositionLocal(int i, double *x, double *y) {\n    const double theta = m_rodJournalAngles[i];\n\n    *x = std::cos(theta) * m_throw;\n    *y = std::sin(theta) * m_throw;\n}\n\nvoid Crankshaft::getRodJournalPositionGlobal(int i, double *x, double *y) {\n    double lx, ly;\n    getRodJournalPositionLocal(i, &lx, &ly);\n\n    *x = lx + m_body.p_x;\n    *y = ly + m_body.p_y;\n}\n\nvoid Crankshaft::resetAngle() {\n    m_body.theta = std::fmod(m_body.theta, 4 * constants::pi);\n}\n\nvoid Crankshaft::setRodJournalAngle(int i, double angle) {\n    assert(i < m_rodJournalCount && i >= 0);\n\n    m_rodJournalAngles[i] = angle;\n}\n\ndouble Crankshaft::getAngle() const {\n    return m_body.theta - m_tdc;\n}\n\ndouble Crankshaft::getCycleAngle(double offset) {\n    const double wrapped = std::fmod(-getAngle() + offset, 4 * constants::pi);\n    return (wrapped < 0)\n        ? wrapped + 4 * constants::pi\n        : wrapped;\n}\n"
  },
  {
    "path": "src/crankshaft_object.cpp",
    "content": "#include \"../include/crankshaft_object.h\"\n\n#include \"../include/cylinder_bank.h\"\n#include \"../include/engine_sim_application.h\"\n#include \"../include/ui_utilities.h\"\n\nCrankshaftObject::CrankshaftObject() {\n    m_crankshaft = nullptr;\n}\n\nCrankshaftObject::~CrankshaftObject() {\n    /* void */\n}\n\nvoid CrankshaftObject::generateGeometry() {\n    /* void */\n}\n\nvoid CrankshaftObject::render(const ViewParameters *view) {\n    if (view->Sublayer != 2) return;\n\n    const ysVector grey0 = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.7f);\n    const ysVector grey1 = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.6f);\n    const ysVector grey2 = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.2f);\n\n    const int journalCount = m_crankshaft->getRodJournalCount();\n    for (int i = 0; i < journalCount; ++i) {\n        const int layer = i;\n        if (layer > view->Layer1 || layer < view->Layer0) continue;\n\n        const ysVector col = tintByLayer(grey0, layer - view->Layer0);\n\n        resetShader();\n        setTransform(\n            &m_crankshaft->m_body,\n            (float)m_crankshaft->getThrow(),\n            0.0f,\n            0.0f,\n            (float)m_crankshaft->getRodJournalAngle(i));\n\n        m_app->getShaders()->SetBaseColor(col);\n        m_app->getEngine()->DrawModel(\n            m_app->getShaders()->GetRegularFlags(),\n            m_app->getAssetManager()->GetModelAsset(\"Crankshaft\"),\n            0x32 - layer);\n    }\n\n    setTransform(\n        &m_crankshaft->m_body,\n        (float)m_crankshaft->getThrow(),\n        0.0f,\n        0.0f,\n        0.0f);\n    m_app->getShaders()->SetBaseColor(grey1);\n    m_app->getEngine()->DrawModel(\n        m_app->getShaders()->GetRegularFlags(),\n        m_app->getAssetManager()->GetModelAsset(\"CrankSnout\"),\n        0x32);\n\n    m_app->getShaders()->SetBaseColor(grey2);\n    m_app->getEngine()->DrawModel(\n        m_app->getShaders()->GetRegularFlags(),\n        m_app->getAssetManager()->GetModelAsset(\"CrankSnoutThreads\"),\n        0x32);\n}\n\nvoid CrankshaftObject::process(float dt) {\n    /* void */\n}\n\nvoid CrankshaftObject::destroy() {\n    /* void */\n}\n"
  },
  {
    "path": "src/cylinder_bank.cpp",
    "content": "#include \"../include/cylinder_bank.h\"\n\n#include \"../include/constants.h\"\n\n#include <cmath>\n\nCylinderBank::CylinderBank() {\n    m_angle = 0.0;\n    m_bore = 0.0;\n    m_deckHeight = 0.0;\n    m_cylinderCount = 0;\n    m_displayDepth = 0.4;\n    m_index = -1;\n\n    m_dx = m_dy = 0;\n    m_x = m_y = 0;\n}\n\nCylinderBank::~CylinderBank() {\n    /* void */\n}\n\nvoid CylinderBank::initialize(const Parameters &params) {\n    m_angle = params.angle;\n    m_bore = params.bore;\n    m_deckHeight = params.deckHeight;\n    m_cylinderCount = params.cylinderCount;\n\n    m_dx = std::cos(m_angle + constants::pi / 2);\n    m_dy = std::sin(m_angle + constants::pi / 2);\n\n    m_x = params.positionX;\n    m_y = params.positionY;\n\n    m_displayDepth = params.displayDepth;\n\n    m_index = params.index;\n}\n\nvoid CylinderBank::destroy() {\n    /* void */\n}\n\nvoid CylinderBank::getPositionAboveDeck(double h, double *x, double *y) const {\n    *x = m_dx * (m_deckHeight + h) + m_x;\n    *y = m_dy * (m_deckHeight + h) + m_y;\n}\n\ndouble CylinderBank::boreSurfaceArea() const {\n    return constants::pi * m_bore * m_bore / 4.0;\n}\n"
  },
  {
    "path": "src/cylinder_bank_object.cpp",
    "content": "#include \"../include/cylinder_bank_object.h\"\n\n#include \"../include/engine_sim_application.h\"\n\nCylinderBankObject::CylinderBankObject() {\n    m_bank = nullptr;\n    m_head = nullptr;\n}\n\nCylinderBankObject::~CylinderBankObject() {\n    /* void */\n}\n\nvoid CylinderBankObject::generateGeometry() {\n    const double s = m_bank->getBore() / 2.0;\n    const double boreSurfaceArea =\n        constants::pi * m_bank->getBore() * m_bank->getBore() / 4.0;\n    const double chamberHeight = m_head->getCombustionChamberVolume() / boreSurfaceArea;\n\n    GeometryGenerator *gen = m_app->getGeometryGenerator();\n\n    const float displayDepth = 1.0f - static_cast<float>(m_bank->getDisplayDepth());\n    const float lineWidth = (float)(m_bank->getBore() * 0.1);\n    const float margin = lineWidth * 0.25f;\n    const float dx = -(float)(m_bank->getDy() * (margin + m_bank->getBore() / 2 + lineWidth / 2));\n    const float dy = (float)(m_bank->getDx() * (margin + m_bank->getBore() / 2 + lineWidth / 2));\n    const float top_x =\n        (float)(m_bank->getX() + m_bank->getDx() * (m_bank->getDeckHeight() + chamberHeight));\n    const float top_y =\n        (float)(m_bank->getY() + m_bank->getDy() * (m_bank->getDeckHeight() + chamberHeight));\n    const float bottom_x =\n        (float)(m_bank->getX() + m_bank->getDx() * (displayDepth * m_bank->getDeckHeight()));\n    const float bottom_y =\n        (float)(m_bank->getY() + m_bank->getDy() * (displayDepth * m_bank->getDeckHeight()));\n\n    GeometryGenerator::Line2dParameters params;\n    params.lineWidth = lineWidth;\n\n    GeometryGenerator::Circle2dParameters circleParams;\n    circleParams.radius = lineWidth / 2.0f;\n    circleParams.maxEdgeLength = m_app->pixelsToUnits(5.0f);\n\n    gen->startShape();\n\n    params.x0 = top_x + dx;\n    params.y0 = top_y + dy;\n    params.x1 = bottom_x + dx;\n    params.y1 = bottom_y + dy;\n    gen->generateLine2d(params);\n\n    circleParams.center_x = params.x1;\n    circleParams.center_y = params.y1;\n    gen->generateCircle2d(circleParams);\n\n    params.x0 = top_x - dx;\n    params.y0 = top_y - dy;\n    params.x1 = bottom_x - dx;\n    params.y1 = bottom_y - dy;\n    gen->generateLine2d(params);\n\n    circleParams.center_x = params.x1;\n    circleParams.center_y = params.y1;\n    gen->generateCircle2d(circleParams);\n\n    gen->endShape(&m_walls);\n}\n\nvoid CylinderBankObject::render(const ViewParameters *view) {\n    if (view->Sublayer != 0) return;\n\n    resetShader();\n\n    const ysVector col = ysMath::Add(\n        ysMath::Mul(m_app->getForegroundColor(), ysMath::LoadScalar(0.01f)),\n        ysMath::Mul(m_app->getBackgroundColor(), ysMath::LoadScalar(0.99f))\n    );\n\n    m_app->getShaders()->SetBaseColor(m_app->getPink());\n    m_app->drawGenerated(m_walls, 0x0);\n}\n\nvoid CylinderBankObject::process(float dt) {\n    /* void */\n}\n\nvoid CylinderBankObject::destroy() {\n    /* void */\n}\n"
  },
  {
    "path": "src/cylinder_head.cpp",
    "content": "#include \"../include/cylinder_head.h\"\n\n#include \"../include/cylinder_bank.h\"\n#include \"../include/valvetrain.h\"\n\n#include <assert.h>\n\nCylinderHead::CylinderHead() {\n    m_cylinders = nullptr;\n\n    m_flipDisplay = false;\n\n    m_bank = nullptr;\n    m_valvetrain = nullptr;\n\n    m_exhaustPortFlow = nullptr;\n    m_intakePortFlow = nullptr;\n\n    m_intakeRunnerVolume = 0.0;\n    m_intakeRunnerCrossSectionArea = 0.0;\n    m_exhaustRunnerVolume = 0.0;\n    m_exhaustRunnerCrossSectionArea = 0.0;\n    m_combustionChamberVolume = 0.0;\n}\n\nCylinderHead::~CylinderHead() {\n    /* void */\n}\n\nvoid CylinderHead::initialize(const Parameters &params) {\n    m_cylinders = new Cylinder[params.Bank->getCylinderCount()];\n\n    m_bank = params.Bank;\n    m_valvetrain = params.Valvetrain;\n    m_exhaustPortFlow = params.ExhaustPortFlow;\n    m_intakePortFlow = params.IntakePortFlow;\n    m_combustionChamberVolume = params.CombustionChamberVolume;\n    m_flipDisplay = params.FlipDisplay;\n\n    m_intakeRunnerVolume = params.IntakeRunnerVolume;\n    m_intakeRunnerCrossSectionArea = params.IntakeRunnerCrossSectionArea;\n    m_exhaustRunnerVolume = params.ExhaustRunnerVolume;\n    m_exhaustRunnerCrossSectionArea = params.ExhaustRunnerCrossSectionArea;\n}\n\nvoid CylinderHead::destroy() {\n    if (m_cylinders != nullptr) delete[] m_cylinders;\n    m_cylinders = nullptr;\n}\n\ndouble CylinderHead::intakeFlowRate(int cylinder) const {\n    return m_intakePortFlow->sampleTriangle(\n            intakeValveLift(cylinder));\n}\n\ndouble CylinderHead::exhaustFlowRate(int cylinder) const {\n    return m_exhaustPortFlow->sampleTriangle(\n            exhaustValveLift(cylinder));\n}\n\ndouble CylinderHead::intakeValveLift(int cylinder) const {\n    return m_valvetrain->intakeValveLift(cylinder);\n}\n\ndouble CylinderHead::exhaustValveLift(int cylinder) const {\n    return m_valvetrain->exhaustValveLift(cylinder);\n}\n\nvoid CylinderHead::setAllExhaustSystems(ExhaustSystem *system) {\n    for (int i = 0; i < m_bank->getCylinderCount(); ++i) {\n        m_cylinders[i].exhaustSystem = system;\n    }\n}\n\nvoid CylinderHead::setExhaustSystem(int i, ExhaustSystem *system) {\n    m_cylinders[i].exhaustSystem = system;\n}\n\nvoid CylinderHead::setSoundAttenuation(int i, double soundAttenuation) {\n    m_cylinders[i].soundAttenuation = soundAttenuation;\n}\n\nvoid CylinderHead::setAllIntakes(Intake *intake) {\n    for (int i = 0; i < m_bank->getCylinderCount(); ++i) {\n        m_cylinders[i].intake = intake;\n    }\n}\n\nvoid CylinderHead::setIntake(int i, Intake *intake) {\n    m_cylinders[i].intake = intake;\n}\n\nvoid CylinderHead::setAllHeaderPrimaryLengths(double length) {\n    for (int i = 0; i < m_bank->getCylinderCount(); ++i) {\n        m_cylinders[i].headerPrimaryLength = length;\n    }\n}\n\nvoid CylinderHead::setHeaderPrimaryLength(int i, double length) {\n    m_cylinders[i].headerPrimaryLength = length;\n}\n\nCamshaft *CylinderHead::getExhaustCamshaft() {\n    return m_valvetrain->getActiveExhaustCamshaft();\n}\n\nCamshaft *CylinderHead::getIntakeCamshaft() {\n    return m_valvetrain->getActiveIntakeCamshaft();\n}\n"
  },
  {
    "path": "src/cylinder_head_object.cpp",
    "content": "#include \"../include/cylinder_head_object.h\"\n\n#include \"../include/cylinder_bank.h\"\n#include \"../include/engine_sim_application.h\"\n#include \"../include/constants.h\"\n\nCylinderHeadObject::CylinderHeadObject() {\n    m_head = nullptr;\n    m_engine = nullptr;\n}\n\nCylinderHeadObject::~CylinderHeadObject() {\n    /* void */\n}\n\nvoid CylinderHeadObject::generateGeometry() {\n    /* void */\n}\n\nvoid CylinderHeadObject::render(const ViewParameters *view) {\n    if (view->Sublayer != 0) return;\n\n    resetShader();\n\n    CylinderBank *bank = m_head->getCylinderBank();\n    const double s = (float)bank->getBore() / 2.0f;\n    const double boreSurfaceArea =\n        constants::pi * bank->getBore() * bank->getBore() / 4.0;\n    const double chamberHeight = m_head->getCombustionChamberVolume() / boreSurfaceArea;\n\n    Piston *frontmostPiston = getForemostPiston(bank, view->Layer0);\n    if (frontmostPiston == nullptr) return;\n\n    const double theta = bank->getAngle();\n    double x, y;\n    bank->getPositionAboveDeck(chamberHeight, &x, &y);\n\n    const ysMatrix scale = ysMath::ScaleTransform(ysMath::LoadScalar((float)s));\n    const ysMatrix rotation = ysMath::RotationTransform(\n            ysMath::Constants::ZAxis, (float)theta);\n    const ysMatrix translation = ysMath::TranslationTransform(\n            ysMath::LoadVector((float)x, (float)y));\n    const ysMatrix T_headObject = ysMath::MatMult(translation, rotation);\n    const ysMatrix T_head = ysMath::MatMult(\n            T_headObject,\n            scale);\n\n    const ysVector col = m_app->getPink();  ysMath::Add(\n        ysMath::Mul(m_app->getForegroundColor(), ysMath::LoadScalar(0.01f)),\n        ysMath::Mul(m_app->getBackgroundColor(), ysMath::LoadScalar(0.99f))\n    );\n    const ysVector moving = m_app->getForegroundColor();\n\n    GeometryGenerator::GeometryIndices\n        valveShadow,\n        valveRoller,\n        valveRollerShadow,\n        valveRollerPin,\n        camCenter;\n    GeometryGenerator *gen = m_app->getGeometryGenerator();\n    GeometryGenerator::Line2dParameters params;\n\n    gen->startShape();\n    params.lineWidth = 0.2f;\n    params.x0 = -0.689352f;\n    params.y0 = 0.085f;\n    params.x1 = -0.88632f;\n    params.y1 = -0.077975f;\n    gen->generateLine2d(params);\n\n    params.x0 = -(params.x0 + 0.5f) - 0.5f;\n    params.x1 = -(params.x1 + 0.5f) - 0.5f;\n    gen->generateLine2d(params);\n\n    params.x0 = 0.689352f;\n    params.x1 = 0.88632f;\n    gen->generateLine2d(params);\n\n    params.x0 = -(params.x0 - 0.5f) + 0.5f;\n    params.x1 = -(params.x1 - 0.5f) + 0.5f;\n    gen->generateLine2d(params);\n\n    params.lineWidth = 0.0917f + m_app->pixelsToUnits(5.0f) / (float)s;\n    params.x0 = -0.5f;\n    params.y0 = 0.2f;\n    params.x1 = -0.5f;\n    params.y1 = 1.5f;\n    gen->generateLine2d(params);\n\n    params.x0 = 0.5f;\n    params.x1 = 0.5f;\n    gen->generateLine2d(params);\n\n    gen->endShape(&valveShadow);\n\n    constexpr float rollerRadius = (float)units::distance(300.0, units::thou);\n    GeometryGenerator::Circle2dParameters circleParams;\n    circleParams.radius = rollerRadius / (float)s;\n    circleParams.center_x = 0.0f;\n    circleParams.center_y = 1.99f;\n    gen->startShape();\n    gen->generateCircle2d(circleParams);\n    gen->endShape(&valveRoller);\n\n    circleParams.radius = (rollerRadius + m_app->pixelsToUnits(5.0f) / 2) / (float)s;\n    gen->startShape();\n    gen->generateCircle2d(circleParams);\n    gen->endShape(&valveRollerShadow);\n\n    circleParams.radius = (rollerRadius * 0.25f) / (float)s;\n    gen->startShape();\n    gen->generateCircle2d(circleParams);\n    gen->endShape(&valveRollerPin);\n\n    circleParams.radius = (rollerRadius * 0.25f);\n    circleParams.center_x = 0.0f;\n    circleParams.center_y = 0.0f;\n    gen->startShape();\n    gen->generateCircle2d(circleParams);\n    gen->endShape(&camCenter);\n\n    m_app->getShaders()->SetObjectTransform(T_head);\n    m_app->getShaders()->SetBaseColor(col);\n    m_app->getEngine()->DrawModel(\n        m_app->getShaders()->GetRegularFlags(),\n        m_app->getAssetManager()->GetModelAsset(\"CylinderHead\"),\n        0x0);\n    m_app->getShaders()->SetObjectTransform(T_head);\n    m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor());\n    m_app->drawGenerated(valveShadow, 0x1);\n\n    const double intakeValvePosition = (m_head->getFlipDisplay())\n        ? 0.5f\n        : -0.5f;\n\n    const int layer = frontmostPiston->getCylinderIndex();\n\n    const float intakeLift = (float)m_head->intakeValveLift(layer);\n    const ysMatrix T_intakeValve = ysMath::MatMult(\n            T_head,\n            ysMath::TranslationTransform(\n                ysMath::LoadVector(\n                    (float)intakeValvePosition,\n                    (float)(-intakeLift / s),\n                    0.0f,\n                    0.0f)));\n\n    m_app->getShaders()->SetObjectTransform(T_intakeValve);\n    m_app->getShaders()->SetBaseColor(m_app->getBlue());\n    m_app->getEngine()->DrawModel(\n        m_app->getShaders()->GetRegularFlags(),\n        m_app->getAssetManager()->GetModelAsset(\"Valve\"),\n        0x33);\n    m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor());\n    m_app->drawGenerated(valveRollerShadow, 0x33);\n    m_app->getShaders()->SetBaseColor(m_app->getBlue());\n    m_app->drawGenerated(valveRoller, 0x33);\n    m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor());\n    m_app->drawGenerated(valveRollerPin, 0x33);\n\n    const double exhaustLift = (float)m_head->exhaustValveLift(layer);\n    const ysMatrix T_exhaustValve = ysMath::MatMult(\n        T_head,\n        ysMath::TranslationTransform(\n            ysMath::LoadVector(\n                (float)(-intakeValvePosition),\n                (float)(-exhaustLift / s),\n                0.0f,\n                0.0f)));\n\n    m_app->getShaders()->SetObjectTransform(T_exhaustValve);\n    m_app->getShaders()->SetBaseColor(m_app->getYellow());\n    m_app->getEngine()->DrawModel(\n        m_app->getShaders()->GetRegularFlags(),\n        m_app->getAssetManager()->GetModelAsset(\"Valve\"),\n        0x33);\n    m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor());\n    m_app->drawGenerated(valveRollerShadow, 0x33);\n    m_app->getShaders()->SetBaseColor(m_app->getYellow());\n    m_app->drawGenerated(valveRoller, 0x33);\n    m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor());\n    m_app->drawGenerated(valveRollerPin, 0x33);\n\n    Camshaft *intakeCam = m_head->getIntakeCamshaft();\n    Camshaft *exhaustCam = m_head->getExhaustCamshaft();\n    GeometryGenerator::GeometryIndices intake, intakeShadow, exhaust;\n    generateCamshaft(intakeCam, 0.0, rollerRadius, &intake);\n    generateCamshaft(intakeCam, m_app->pixelsToUnits(5.0) / 2, rollerRadius, &intakeShadow);\n    generateCamshaft(exhaustCam, 0.0, rollerRadius, &exhaust);\n\n    ysMatrix T_exhaustCam = ysMath::MatMult(\n        T_headObject,\n        ysMath::TranslationTransform(ysMath::LoadVector(\n            (float)(-intakeValvePosition * s),\n            m_app->pixelsToUnits(5.0f) / 2 + (float)(1.99 * s + exhaustCam->getBaseRadius() + rollerRadius),\n            0.0f,\n            0.0f)));\n    T_exhaustCam = ysMath::MatMult(\n        T_exhaustCam,\n        ysMath::RotationTransform(\n            ysMath::Constants::ZAxis,\n            (float)(\n                exhaustCam->getAngle()\n                + exhaustCam->getLobeCenterline(layer))));\n\n    m_app->getShaders()->SetObjectTransform(T_exhaustCam);\n    m_app->getShaders()->SetBaseColor(m_app->getYellow());\n    m_app->drawGenerated(exhaust);\n    m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor());\n    m_app->drawGenerated(camCenter);\n\n    ysMatrix T_intakeCam = ysMath::MatMult(\n        T_headObject,\n        ysMath::TranslationTransform(ysMath::LoadVector(\n            (float)(intakeValvePosition * s),\n            rollerRadius + m_app->pixelsToUnits(5.0f) / 2 + (float)(1.99 * s + intakeCam->getBaseRadius()),\n            0.0f,\n            0.0f)));\n    T_intakeCam = ysMath::MatMult(\n        T_intakeCam,\n        ysMath::RotationTransform(\n            ysMath::Constants::ZAxis,\n            (float)(\n                intakeCam->getAngle()\n                + intakeCam->getLobeCenterline(layer))));\n\n    m_app->getShaders()->SetObjectTransform(T_intakeCam);\n    m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor());\n    m_app->drawGenerated(intakeShadow);\n\n    m_app->getShaders()->SetObjectTransform(T_intakeCam);\n    m_app->getShaders()->SetBaseColor(m_app->getBlue());\n    m_app->drawGenerated(intake);\n    m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor());\n    m_app->drawGenerated(camCenter);\n}\n\nvoid CylinderHeadObject::process(float dt) {\n    /* void */\n}\n\nvoid CylinderHeadObject::destroy() {\n    /* void */\n}\n\nvoid CylinderHeadObject::generateCamshaft(\n    Camshaft *camshaft,\n    double padding,\n    double rollerRadius,\n    GeometryGenerator::GeometryIndices *indices)\n{\n    GeometryGenerator::Cam2dParameters params;\n    params.baseRadius = (float)(camshaft->getBaseRadius() + padding);\n    params.center_x = 0.0f;\n    params.center_y = 0.0f;\n    params.rollerRadius = (float)rollerRadius;\n    params.lift = camshaft->getLobeProfile();\n    params.maxEdgeLength = (float)units::distance(50, units::thou);\n\n    m_app->getGeometryGenerator()->startShape();\n    m_app->getGeometryGenerator()->generateCam(params);\n    m_app->getGeometryGenerator()->endShape(indices);\n}\n"
  },
  {
    "path": "src/cylinder_pressure_gauge.cpp",
    "content": "#include \"../include/cylinder_pressure_gauge.h\"\n\n#include \"../include/units.h\"\n#include \"../include/gauge.h\"\n#include \"../include/constants.h\"\n#include \"../include/engine_sim_application.h\"\n\n#include <sstream>\n\nCylinderPressureGauge::CylinderPressureGauge() {\n    m_engine = nullptr;\n}\n\nCylinderPressureGauge::~CylinderPressureGauge() {\n    /* void */\n}\n\nvoid CylinderPressureGauge::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n}\n\nvoid CylinderPressureGauge::destroy() {\n    UiElement::destroy();\n}\n\nvoid CylinderPressureGauge::update(float dt) {\n    UiElement::update(dt);\n}\n\nvoid CylinderPressureGauge::render() {\n    drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor());\n\n    const Bounds title = m_bounds.verticalSplit(1.0f, 0.9f);\n    const Bounds body = m_bounds.verticalSplit(0.0f, 0.9f);\n\n    drawCenteredText(\"Cyl. Press. [PSI]\", title.inset(10.0f), 24.0f);\n\n    const int banks = m_engine->getCylinderBankCount();\n\n    Grid grid;\n    grid.h_cells = banks;\n    grid.v_cells = 1;\n\n    while (m_gauges.size() < m_engine->getCylinderCount()) {\n        m_gauges.push_back(addElement<Gauge>());\n    }\n\n    for (int i = 0; i < m_engine->getCylinderCount(); ++i) {\n        Piston *piston = m_engine->getPiston(i);\n        CombustionChamber *chamber = m_engine->getChamber(i);\n        const int bankIndex = piston->getCylinderBank()->getIndex();\n\n        const Bounds &b = grid.get(body, bankIndex, 0);\n\n        Grid bankGrid = { 1, piston->getCylinderBank()->getCylinderCount() };\n        const Bounds &b_cyl =\n            bankGrid.get(\n                b,\n                0,\n                piston->getCylinderBank()->getCylinderCount() - piston->getCylinderIndex() - 1).inset(5.0f);\n\n        const double value = units::convert(chamber->m_system.pressure(), units::psi);\n\n        std::stringstream ss;\n        ss << std::lround(value);\n        drawCenteredText(ss.str(), b_cyl.verticalSplit(0.0f, 2 / 6.0f), b_cyl.height() / 6);\n\n        m_gauges[i]->m_bounds = b_cyl;\n        m_gauges[i]->setLocalPosition({ 0, 0 });\n        m_gauges[i]->m_min = 0;\n        m_gauges[i]->m_max = 1000;\n        m_gauges[i]->m_minorStep = 20;\n        m_gauges[i]->m_majorStep = 100;\n        m_gauges[i]->m_maxMinorTick = 400;\n        m_gauges[i]->m_thetaMin = (float)constants::pi * 1.2f;\n        m_gauges[i]->m_thetaMax = -(float)constants::pi * 0.2f;\n        m_gauges[i]->m_outerRadius = std::fmin(b_cyl.width(), b_cyl.height()) / 2.0f;\n        m_gauges[i]->m_value = (float)units::convert(chamber->m_system.pressure(), units::psi);\n        m_gauges[i]->m_needleOuterRadius = m_gauges[i]->m_outerRadius * 0.7f;\n        m_gauges[i]->m_needleInnerRadius = -m_gauges[i]->m_outerRadius * 0.1f;\n        m_gauges[i]->m_needleWidth = 2.0;\n        m_gauges[i]->m_gamma = 0.5f;\n        m_gauges[i]->setBandCount(2);\n        m_gauges[i]->setBand({ ysMath::Constants::One, 400, 1000, 3.0f }, 0);\n        m_gauges[i]->setBand({ ysColor::srgbiToLinear(0x77CEE0), 0.0f, 14.6959f, 3.0f, 0.0f }, 1);\n    }\n\n    UiElement::render();\n}\n"
  },
  {
    "path": "src/cylinder_temperature_gauge.cpp",
    "content": "#include \"../include/cylinder_temperature_gauge.h\"\n\n#include \"../include/units.h\"\n#include \"../include/gauge.h\"\n#include \"../include/constants.h\"\n#include \"../include/engine_sim_application.h\"\n#include \"../include/ui_utilities.h\"\n\n#include <sstream>\n\n#undef min\n\nCylinderTemperatureGauge::CylinderTemperatureGauge() {\n    m_engine = nullptr;\n    m_maxTemperature = 2000.0;\n    m_minTemperature = 200.0;\n}\n\nCylinderTemperatureGauge::~CylinderTemperatureGauge() {\n    /* void */\n}\n\nvoid CylinderTemperatureGauge::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n}\n\nvoid CylinderTemperatureGauge::destroy() {\n    UiElement::destroy();\n}\n\nvoid CylinderTemperatureGauge::update(float dt) {\n    UiElement::update(dt);\n\n    const double decay = dt / (0.0001 + dt);\n\n    double maxTemperature = m_maxTemperature;\n    double minTemperature = m_minTemperature;\n\n    for (int i = 0; i < m_engine->getCylinderCount(); ++i) {\n        Piston *piston = m_engine->getPiston(i);\n        CombustionChamber *chamber = m_engine->getChamber(i);\n\n        const double temperature = chamber->m_system.temperature();\n        double value = temperature - m_minTemperature;\n\n        m_maxTemperature = std::fmax(m_maxTemperature, value);\n        m_minTemperature = std::fmin(m_minTemperature, temperature);\n    }\n\n    m_maxTemperature *= decay;\n    m_minTemperature *= decay;\n}\n\nvoid CylinderTemperatureGauge::render() {\n    drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor());\n\n    const Bounds title = m_bounds.verticalSplit(1.0f, 0.9f);\n    const Bounds body = m_bounds.verticalSplit(0.0f, 0.9f);\n\n    drawCenteredText(\"Cyl. Temp.\", title.inset(10.0f), 24.0f);\n\n    const int banks = m_engine->getCylinderBankCount();\n\n    Grid grid;\n    grid.h_cells = banks;\n    grid.v_cells = 1;\n\n    GeometryGenerator *generator = m_app->getGeometryGenerator();\n\n    const ysVector background = m_app->getBackgroundColor();\n    const ysVector hot = mix(background, m_app->getRed(), 0.1f);\n    const ysVector cold = mix(background, m_app->getBlue(), 0.001f);\n\n    for (int i = 0; i < m_engine->getCylinderCount(); ++i) {\n        Piston *piston = m_engine->getPiston(i);\n        CombustionChamber *chamber = m_engine->getChamber(i);\n        CylinderBank *bank = piston->getCylinderBank();\n        const int bankIndex = bank->getIndex();\n\n        const Bounds &b = grid.get(body, bankIndex, 0);\n\n        Grid bankGrid = { 1, bank->getCylinderCount() };\n        const Bounds &b_cyl =\n            bankGrid.get(\n                b,\n                0,\n                bank->getCylinderCount() - piston->getCylinderIndex() - 1).inset(5.0f);\n\n        const double temperature = chamber->m_system.temperature();\n        double value = temperature - m_minTemperature;\n\n        const Bounds worldBounds = getRenderBounds(b_cyl);\n        const Point position = worldBounds.getPosition(Bounds::center);\n\n        GeometryGenerator::Circle2dParameters params;\n        params.center_x = position.x;\n        params.center_y = position.y;\n        params.radius = (worldBounds.height() / 2) * 0.9f;\n        params.maxEdgeLength = pixelsToUnits(5.0f);\n\n        GeometryGenerator::GeometryIndices indices;\n        generator->startShape();\n        generator->generateCircle2d(params);\n        generator->endShape(&indices);\n\n        m_app->getShaders()->SetBaseColor(mix(cold, hot, (float)(value / m_maxTemperature)));\n        m_app->drawGenerated(indices, 0x11, m_app->getShaders()->GetUiFlags());\n    }\n\n    UiElement::render();\n}\n"
  },
  {
    "path": "src/delay_filter.cpp",
    "content": "#include \"../include/delay_filter.h\"\n\n"
  },
  {
    "path": "src/derivative_filter.cpp",
    "content": "#include \"../include/derivative_filter.h\"\n\nDerivativeFilter::DerivativeFilter() {\n    m_previous = 0;\n    m_dt = 0;\n}\n\nDerivativeFilter::~DerivativeFilter() {\n    /* void */\n}\n\nfloat DerivativeFilter::f(float sample) {\n    const float temp = m_previous;\n    m_previous = sample;\n\n    return (sample - temp) / m_dt;\n}\n"
  },
  {
    "path": "src/direct_throttle_linkage.cpp",
    "content": "#include \"../include/direct_throttle_linkage.h\"\n\n#include \"../include/engine.h\"\n\n#include <cmath>\n\nDirectThrottleLinkage::DirectThrottleLinkage() {\n    m_gamma = 1.0;\n    m_throttlePosition = 1.0;\n}\n\nDirectThrottleLinkage::~DirectThrottleLinkage() {\n    /* void */\n}\n\nvoid DirectThrottleLinkage::initialize(const Parameters &params) {\n    m_gamma = params.gamma;\n}\n\nvoid DirectThrottleLinkage::setSpeedControl(double s) {\n    Throttle::setSpeedControl(s);\n    m_throttlePosition = 1 - std::pow(s, m_gamma);\n}\n\nvoid DirectThrottleLinkage::update(double dt, Engine *engine) {\n    Throttle::update(dt, engine);\n    engine->setThrottle(m_throttlePosition);\n}\n"
  },
  {
    "path": "src/dynamometer.cpp",
    "content": "#include \"../include/dynamometer.h\"\n\n#include \"../include/units.h\"\n\n#include <cmath>\n\nDynamometer::Dynamometer() : atg_scs::Constraint(1, 1) {\n    m_rotationSpeed = 0.0;\n    m_ks = 10.0;\n    m_kd = 1.0;\n    m_maxTorque = units::torque(10000.0, units::ft_lb);\n\n    m_enabled = false;\n    m_hold = false;\n}\n\nDynamometer::~Dynamometer() {\n    /* void */\n}\n\nvoid Dynamometer::connectCrankshaft(Crankshaft *crankshaft) {\n    m_bodies[0] = &crankshaft->m_body;\n}\n\nvoid Dynamometer::calculate(Output *output, atg_scs::SystemState *state) {\n    output->J[0][0] = 0;\n    output->J[0][1] = 0;\n    output->J[0][2] = 1;\n\n    output->J_dot[0][0] = 0;\n    output->J_dot[0][1] = 0;\n    output->J_dot[0][2] = 0;\n\n    output->ks[0] = m_ks;\n    output->kd[0] = m_kd;\n\n    output->C[0] = 0;\n\n    if (m_bodies[0]->v_theta < 0) {\n        output->v_bias[0] = m_rotationSpeed;\n        output->limits[0][0] = (m_hold && m_enabled) ? -m_maxTorque : 0.0;\n        output->limits[0][1] = m_enabled ? m_maxTorque : 0.0;\n    }\n    else {\n        output->v_bias[0] = -m_rotationSpeed;\n        output->limits[0][0] = m_enabled ? -m_maxTorque : 0.0;\n        output->limits[0][1] = (m_hold && m_enabled) ? m_maxTorque : 0.0;\n    }\n}\n\ndouble Dynamometer::getTorque() const {\n    return (m_bodies[0]->v_theta > 0)\n        ? -atg_scs::Constraint::F_t[0][0]\n        : atg_scs::Constraint::F_t[0][0];\n}\n"
  },
  {
    "path": "src/engine.cpp",
    "content": "#include \"..\\include\\engine.h\"\n#include \"../include/engine.h\"\n\n#include \"../include/constants.h\"\n#include \"../include/units.h\"\n#include \"../include/fuel.h\"\n#include \"../include/piston_engine_simulator.h\"\n\n#include <cmath>\n#include <assert.h>\n\nEngine::Engine() {\n    m_name = \"\";\n\n    m_crankshafts = nullptr;\n    m_cylinderBanks = nullptr;\n    m_heads = nullptr;\n    m_pistons = nullptr;\n    m_connectingRods = nullptr;\n    m_exhaustSystems = nullptr;\n    m_intakes = nullptr;\n    m_combustionChambers = nullptr;\n\n    m_crankshaftCount = 0;\n    m_cylinderBankCount = 0;\n    m_cylinderCount = 0;\n    m_intakeCount = 0;\n    m_exhaustSystemCount = 0;\n    m_starterSpeed = 0;\n    m_starterTorque = 0;\n    m_dynoMinSpeed = 0;\n    m_dynoMaxSpeed = 0;\n    m_dynoHoldStep = 0;\n    m_redline = 0;\n\n    m_throttle = nullptr;\n    m_throttleValue = 0.0;\n\n    m_initialSimulationFrequency = 10000.0;\n    m_initialHighFrequencyGain = 0.01;\n    m_initialJitter = 0.5;\n    m_initialNoise = 1.0;\n}\n\nEngine::~Engine() {\n    assert(m_crankshafts == nullptr);\n    assert(m_cylinderBanks == nullptr);\n    assert(m_pistons == nullptr);\n    assert(m_connectingRods == nullptr);\n    assert(m_heads == nullptr);\n    assert(m_exhaustSystems == nullptr);\n    assert(m_intakes == nullptr);\n    assert(m_combustionChambers == nullptr);\n}\n\nvoid Engine::initialize(const Parameters &params) {\n    m_crankshaftCount = params.crankshaftCount;\n    m_cylinderCount = params.cylinderCount;\n    m_cylinderBankCount = params.cylinderBanks;\n    m_exhaustSystemCount = params.exhaustSystemCount;\n    m_intakeCount = params.intakeCount;\n    m_starterTorque = params.starterTorque;\n    m_starterSpeed = params.starterSpeed;\n    m_dynoMinSpeed = params.dynoMinSpeed;\n    m_dynoMaxSpeed = params.dynoMaxSpeed;\n    m_dynoHoldStep = params.dynoHoldStep;\n    m_redline = params.redline;\n    m_name = params.name;\n    m_throttle = params.throttle;\n    m_initialHighFrequencyGain = params.initialHighFrequencyGain;\n    m_initialSimulationFrequency = params.initialSimulationFrequency;\n    m_initialJitter = params.initialJitter;\n    m_initialNoise = params.initialNoise;\n\n    m_crankshafts = new Crankshaft[m_crankshaftCount];\n    m_cylinderBanks = new CylinderBank[m_cylinderBankCount];\n    m_heads = new CylinderHead[m_cylinderBankCount];\n    m_pistons = new Piston[m_cylinderCount];\n    m_connectingRods = new ConnectingRod[m_cylinderCount];\n    m_exhaustSystems = new ExhaustSystem[m_exhaustSystemCount];\n    m_intakes = new Intake[m_intakeCount];\n    m_combustionChambers = new CombustionChamber[m_cylinderCount];\n\n    for (int i = 0; i < m_exhaustSystemCount; ++i) {\n        m_exhaustSystems[i].m_index = i;\n    }\n\n    for (int i = 0; i < m_cylinderCount; ++i) {\n        m_combustionChambers[i].setEngine(this);\n    }\n}\n\nvoid Engine::destroy() {\n    for (int i = 0; i < m_crankshaftCount; ++i) {\n        m_crankshafts[i].destroy();\n    }\n\n    for (int i = 0; i < m_cylinderCount; ++i) {\n        m_pistons[i].destroy();\n        m_connectingRods[i].destroy();\n        m_combustionChambers[i].destroy();\n    }\n\n    for (int i = 0; i < m_exhaustSystemCount; ++i) {\n        m_exhaustSystems[i].destroy();\n    }\n\n    for (int i = 0; i < m_intakeCount; ++i) {\n        m_intakes[i].destroy();\n    }\n\n    m_ignitionModule.destroy();\n\n    if (m_throttle != nullptr) delete m_throttle;\n    if (m_crankshafts != nullptr) delete[] m_crankshafts;\n    if (m_cylinderBanks != nullptr) delete[] m_cylinderBanks;\n    if (m_heads != nullptr) delete[] m_heads;\n    if (m_pistons != nullptr) delete[] m_pistons;\n    if (m_connectingRods != nullptr) delete[] m_connectingRods;\n    if (m_exhaustSystems != nullptr) delete[] m_exhaustSystems;\n    if (m_intakes != nullptr) delete[] m_intakes;\n    if (m_combustionChambers != nullptr) delete[] m_combustionChambers;\n\n    m_crankshafts = nullptr;\n    m_cylinderBanks = nullptr;\n    m_pistons = nullptr;\n    m_connectingRods = nullptr;\n    m_heads = nullptr;\n    m_exhaustSystems = nullptr;\n    m_intakes = nullptr;\n    m_combustionChambers = nullptr;\n    m_throttle = nullptr;\n}\n\nCrankshaft *Engine::getOutputCrankshaft() const {\n    return &m_crankshafts[0];\n}\n\nvoid Engine::setSpeedControl(double s) {\n    m_throttle->setSpeedControl(s);\n}\n\ndouble Engine::getSpeedControl() {\n    return m_throttle->getSpeedControl();\n}\n\nvoid Engine::setThrottle(double throttle) {\n    for (int i = 0; i < m_intakeCount; ++i) {\n        m_intakes[i].m_throttle = throttle;\n    }\n\n    m_throttleValue = throttle;\n}\n\ndouble Engine::getThrottle() const {\n    return m_throttleValue;\n}\n\ndouble Engine::getThrottlePlateAngle() const {\n    return (1 - m_intakes[0].getThrottlePlatePosition()) * (constants::pi / 2);\n}\n\nbool placeRod(\n    const ConnectingRod &rod,\n    const CylinderBank &bank,\n    const Crankshaft &crankshaft,\n    double crankshaftAngle,\n    double *p_x,\n    double *p_y,\n    double *theta,\n    double *s)\n{\n    double p_x_0, p_y_0, l_x, l_y, theta_0;\n    if (rod.getMasterRod() != nullptr) {\n        double s;\n        const bool succeeded = placeRod(\n            *rod.getMasterRod(),\n            *rod.getMasterRod()->getPiston()->getCylinderBank(),\n            *rod.getCrankshaft(),\n            crankshaftAngle,\n            &p_x_0,\n            &p_y_0,\n            &theta_0,\n            &s);\n\n        if (!succeeded) {\n            return false;\n        }\n\n        rod.getMasterRod()->getRodJournalPositionLocal(rod.getPiston()->getCylinderIndex(), &l_x, &l_y);\n    }\n    else {\n        theta_0 = crankshaftAngle;\n        p_x_0 = rod.getCrankshaft()->getPosX();\n        p_y_0 = rod.getCrankshaft()->getPosY();\n        rod.getCrankshaft()->getRodJournalPositionLocal(rod.getPiston()->getCylinderIndex(), &l_x, &l_y);\n    }\n\n    const double dx = std::cos(theta_0);\n    const double dy = std::sin(theta_0);\n    *p_x = p_x_0 + (dx * l_x - dy * l_y);\n    *p_y = p_y_0 + (dy * l_x + dx * l_y);\n\n    // (bank->m_x + bank->m_dx * s - p_x)^2 + (bank->m_y + bank->m_dy * s - p_y)^2 = (rod->m_length)^2\n    const double a = bank.getDx() * bank.getDx() + bank.getDy() * bank.getDy();\n    const double b = -2 * bank.getDx() * ((*p_x) - bank.getX()) - 2 * bank.getDy() * ((*p_y) - bank.getY());\n    const double c =\n        ((*p_x) - bank.getX()) * ((*p_x) - bank.getX())\n        + ((*p_y) - bank.getY()) * ((*p_y) - bank.getY())\n        - rod.getLength() * rod.getLength();\n\n    const double det = b * b - 4 * a * c;\n    if (det < 0) return false;\n\n    const double sqrt_det = std::sqrt(det);\n    const double s0 = (-b + sqrt_det) / (2 * a);\n    const double s1 = (-b - sqrt_det) / (2 * a);\n\n    *s = std::max(s0, s1);\n    if (*s < 0) return false;\n   \n    if (s != nullptr) {\n        const double dx = (bank.getX() + bank.getDx() * (*s)) - (*p_x);\n        const double dy = (bank.getY() + bank.getDy() * (*s)) - (*p_y);\n\n        *theta = (dy > 0)\n            ? std::acos(dx)\n            : -std::acos(dx);\n    }\n\n    return true;\n}\n\nvoid Engine::calculateDisplacement() {\n    // There is a closed-form/correct way to do this which I really\n    // don't feel like deriving right now, so I'm just going with this\n    // numerical approximation.\n    constexpr int Resolution = 1000;\n\n    double *min_s = new double[m_cylinderCount];\n    double *max_s = new double[m_cylinderCount];\n\n    for (int i = 0; i < m_cylinderCount; ++i) {\n        min_s[i] = DBL_MAX;\n        max_s[i] = -DBL_MAX;\n    }\n\n    for (int j = 0; j < Resolution; ++j) {\n        const double crankshaftAngle = 2 * (j / static_cast<double>(Resolution)) * constants::pi;\n\n        for (int i = 0; i < m_cylinderCount; ++i) {\n            const Piston &piston = m_pistons[i];\n            const CylinderBank &bank = *piston.getCylinderBank();\n            const ConnectingRod &rod = *piston.getRod();\n            const Crankshaft &shaft = *rod.getCrankshaft();\n\n            double p_x, p_y;\n            double theta;\n            double s;\n            if (!placeRod(\n                rod,\n                bank,\n                shaft,\n                crankshaftAngle,\n                &p_x,\n                &p_y,\n                &theta,\n                &s))\n            {\n                continue;\n            }\n\n            min_s[i] = std::min(min_s[i], s);\n            max_s[i] = std::max(max_s[i], s);\n        }\n    }\n\n    double displacement = 0;\n    for (int i = 0; i < m_cylinderCount; ++i) {\n        const Piston &piston = m_pistons[i];\n        const CylinderBank &bank = *piston.getCylinderBank();\n\n        if (min_s[i] < max_s[i]) {\n            const double r = bank.getBore() / 2.0;\n            displacement += constants::pi * r * r * (max_s[i] - min_s[i]);\n        }\n    }\n\n    m_displacement = displacement;\n}\n\ndouble Engine::getIntakeFlowRate() const {\n    double airIntake = 0;\n    for (int i = 0; i < m_intakeCount; ++i) {\n        airIntake += m_intakes[i].m_flowRate;\n    }\n\n    return airIntake;\n}\n\nvoid Engine::update(double dt) {\n    m_throttle->update(dt, this);\n}\n\ndouble Engine::getManifoldPressure() const {\n    double pressureSum = 0.0;\n    for (int i = 0; i < m_intakeCount; ++i) {\n        pressureSum += m_intakes[i].m_system.pressure();\n    }\n\n    return pressureSum / m_intakeCount;\n}\n\ndouble Engine::getIntakeAfr() const {\n    double totalOxygen = 0.0;\n    double totalFuel = 0.0;\n    for (int i = 0; i < m_intakeCount; ++i) {\n        totalOxygen += m_intakes[i].m_system.n_o2();\n        totalFuel += m_intakes[i].m_system.n_fuel();\n    }\n\n    constexpr double octaneMolarMass = units::mass(114.23, units::g);\n    constexpr double oxygenMolarMass = units::mass(31.9988, units::g);\n\n    if (totalFuel == 0) return 0;\n    else {\n        return\n            (oxygenMolarMass * totalOxygen / 0.21)\n            / (totalFuel * octaneMolarMass);\n    }\n}\n\ndouble Engine::getExhaustO2() const {\n    double totalInert = 0.0;\n    double totalOxygen = 0.0;\n    double totalFuel = 0.0;\n    for (int i = 0; i < m_exhaustSystemCount; ++i) {\n        totalInert += m_exhaustSystems[i].m_system.n_inert();\n        totalOxygen += m_exhaustSystems[i].m_system.n_o2();\n        totalFuel += m_exhaustSystems[i].m_system.n_fuel();\n    }\n\n    constexpr double octaneMolarMass = units::mass(114.23, units::g);\n    constexpr double oxygenMolarMass = units::mass(31.9988, units::g);\n    constexpr double nitrogenMolarMass = units::mass(28.014, units::g);\n\n    if (totalFuel == 0) return 0;\n    else {\n        return\n            (oxygenMolarMass * totalOxygen)\n            / (\n                totalFuel * octaneMolarMass\n                + nitrogenMolarMass * totalInert\n                + oxygenMolarMass * totalOxygen);\n    }\n}\n\nvoid Engine::resetFuelConsumption() {\n    for (int i = 0; i < m_intakeCount; ++i) {\n        m_intakes[i].m_totalFuelInjected = 0;\n    }\n}\n\ndouble Engine::getTotalFuelMassConsumed() const {\n    double n_fuelConsumed = 0;\n    for (int i = 0; i < m_intakeCount; ++i) {\n        n_fuelConsumed += m_intakes[i].m_totalFuelInjected;\n    }\n\n    return n_fuelConsumed * m_fuel.getMolecularMass();\n}\n\ndouble Engine::getTotalVolumeFuelConsumed() const {\n    return getTotalFuelMassConsumed() / m_fuel.getDensity();\n}\n\nint Engine::getMaxDepth() const {\n    int maxDepth = 0;\n    for (int i = 0; i < m_crankshaftCount; ++i) {\n        maxDepth = std::max(m_crankshafts[i].getRodJournalCount(), maxDepth);\n    }\n\n    return maxDepth;\n}\n\nSimulator *Engine::createSimulator(Vehicle *vehicle, Transmission *transmission) {\n    PistonEngineSimulator *simulator = new PistonEngineSimulator;\n    Simulator::Parameters simulatorParams;\n    simulatorParams.systemType = Simulator::SystemType::NsvOptimized;\n    simulator->initialize(simulatorParams);\n\n    simulator->loadSimulation(this, vehicle, transmission);\n    simulator->setFluidSimulationSteps(8);\n\n    return static_cast<Simulator *>(simulator);\n}\n\ndouble Engine::getRpm() const {\n    if (m_crankshaftCount == 0) return 0;\n    else return std::abs(units::toRpm(getCrankshaft(0)->m_body.v_theta));\n}\n\ndouble Engine::getSpeed() const {\n    if (m_crankshaftCount == 0) return 0;\n    else return std::abs(getCrankshaft(0)->m_body.v_theta);\n}\n\nbool Engine::isSpinningCw() const {\n    return getOutputCrankshaft()->m_body.v_theta <= 0;\n}\n"
  },
  {
    "path": "src/engine_sim_application.cpp",
    "content": "#include \"../include/engine_sim_application.h\"\n\n#include \"../include/piston_object.h\"\n#include \"../include/connecting_rod_object.h\"\n#include \"../include/constants.h\"\n#include \"../include/units.h\"\n#include \"../include/crankshaft_object.h\"\n#include \"../include/cylinder_bank_object.h\"\n#include \"../include/cylinder_head_object.h\"\n#include \"../include/ui_button.h\"\n#include \"../include/combustion_chamber_object.h\"\n#include \"../include/csv_io.h\"\n#include \"../include/exhaust_system.h\"\n#include \"../include/feedback_comb_filter.h\"\n#include \"../include/utilities.h\"\n\n#include \"../scripting/include/compiler.h\"\n\n#include <chrono>\n#include <stdlib.h>\n#include <sstream>\n\n#if ATG_ENGINE_SIM_DISCORD_ENABLED\n#include \"../discord/Discord.h\"\n#endif\n\nstd::string EngineSimApplication::s_buildVersion = \"0.1.12a\";\n\nEngineSimApplication::EngineSimApplication() {\n    m_assetPath = \"\";\n\n    m_geometryVertexBuffer = nullptr;\n    m_geometryIndexBuffer = nullptr;\n\n    m_paused = false;\n    m_recording = false;\n    m_screenResolutionIndex = 0;\n    for (int i = 0; i < ScreenResolutionHistoryLength; ++i) {\n        m_screenResolution[i][0] = m_screenResolution[i][1] = 0;\n    }\n\n    m_background = ysColor::srgbiToLinear(0x0E1012);\n    m_foreground = ysColor::srgbiToLinear(0xFFFFFF);\n    m_shadow = ysColor::srgbiToLinear(0x0E1012);\n    m_highlight1 = ysColor::srgbiToLinear(0xEF4545);\n    m_highlight2 = ysColor::srgbiToLinear(0xFFFFFF);\n    m_pink = ysColor::srgbiToLinear(0xF394BE);\n    m_red = ysColor::srgbiToLinear(0xEE4445);\n    m_orange = ysColor::srgbiToLinear(0xF4802A);\n    m_yellow = ysColor::srgbiToLinear(0xFDBD2E);\n    m_blue = ysColor::srgbiToLinear(0x77CEE0);\n    m_green = ysColor::srgbiToLinear(0xBDD869);\n\n    m_displayHeight = (float)units::distance(2.0, units::foot);\n    m_outputAudioBuffer = nullptr;\n    m_audioSource = nullptr;\n\n    m_torque = 0;\n    m_dynoSpeed = 0;\n\n    m_simulator = nullptr;\n    m_engineView = nullptr;\n    m_rightGaugeCluster = nullptr;\n    m_temperatureGauge = nullptr;\n    m_oscCluster = nullptr;\n    m_performanceCluster = nullptr;\n    m_loadSimulationCluster = nullptr;\n    m_mixerCluster = nullptr;\n    m_infoCluster = nullptr;\n    m_iceEngine = nullptr;\n    m_mainRenderTarget = nullptr;\n\n    m_vehicle = nullptr;\n    m_transmission = nullptr;\n\n    m_oscillatorSampleOffset = 0;\n    m_gameWindowHeight = 256;\n    m_screenWidth = 256;\n    m_screenHeight = 256;\n    m_screen = 0;\n    m_viewParameters.Layer0 = 0;\n    m_viewParameters.Layer1 = 0;\n\n    m_displayAngle = 0.0f;\n}\n\nEngineSimApplication::~EngineSimApplication() {\n    /* void */\n}\n\nvoid EngineSimApplication::initialize(void *instance, ysContextObject::DeviceAPI api) {\n    dbasic::Path modulePath = dbasic::GetModulePath();\n    dbasic::Path confPath = modulePath.Append(\"delta.conf\");\n\n    std::string enginePath = \"../dependencies/submodules/delta-studio/engines/basic\";\n    m_assetPath = \"../assets\";\n    if (confPath.Exists()) {\n        std::fstream confFile(confPath.ToString(), std::ios::in);\n\n        std::getline(confFile, enginePath);\n        std::getline(confFile, m_assetPath);\n        enginePath = modulePath.Append(enginePath).ToString();\n        m_assetPath = modulePath.Append(m_assetPath).ToString();\n\n        confFile.close();\n    }\n\n    m_engine.GetConsole()->SetDefaultFontDirectory(enginePath + \"/fonts/\");\n\n    const std::string shaderPath = enginePath + \"/shaders/\";\n    const std::string winTitle = \"Engine Sim | AngeTheGreat | v\" + s_buildVersion;\n    dbasic::DeltaEngine::GameEngineSettings settings;\n    settings.API = api;\n    settings.DepthBuffer = false;\n    settings.Instance = instance;\n    settings.ShaderDirectory = shaderPath.c_str();\n    settings.WindowTitle = winTitle.c_str();\n    settings.WindowPositionX = 0;\n    settings.WindowPositionY = 0;\n    settings.WindowStyle = ysWindow::WindowStyle::Windowed;\n    settings.WindowWidth = 1920;\n    settings.WindowHeight = 1080;\n\n    m_engine.CreateGameWindow(settings);\n\n    m_engine.GetDevice()->CreateSubRenderTarget(\n        &m_mainRenderTarget,\n        m_engine.GetScreenRenderTarget(),\n        0,\n        0,\n        0,\n        0);\n\n    m_engine.InitializeShaderSet(&m_shaderSet);\n    m_shaders.Initialize(\n        &m_shaderSet,\n        m_mainRenderTarget,\n        m_engine.GetScreenRenderTarget(),\n        m_engine.GetDefaultShaderProgram(),\n        m_engine.GetDefaultInputLayout());\n    m_engine.InitializeConsoleShaders(&m_shaderSet);\n    m_engine.SetShaderSet(&m_shaderSet);\n\n    m_shaders.SetClearColor(ysColor::srgbiToLinear(0x34, 0x98, 0xdb));\n\n    m_assetManager.SetEngine(&m_engine);\n\n    m_engine.GetDevice()->CreateIndexBuffer(\n        &m_geometryIndexBuffer, sizeof(unsigned short) * 200000, nullptr);\n    m_engine.GetDevice()->CreateVertexBuffer(\n        &m_geometryVertexBuffer, sizeof(dbasic::Vertex) * 100000, nullptr);\n\n    m_geometryGenerator.initialize(100000, 200000);\n\n    initialize();\n}\n\nvoid EngineSimApplication::initialize() {\n    m_shaders.SetClearColor(ysColor::srgbiToLinear(0x34, 0x98, 0xdb));\n    m_assetManager.CompileInterchangeFile((m_assetPath + \"/assets\").c_str(), 1.0f, true);\n    m_assetManager.LoadSceneFile((m_assetPath + \"/assets\").c_str(), true);\n\n    m_textRenderer.SetEngine(&m_engine);\n    m_textRenderer.SetRenderer(m_engine.GetUiRenderer());\n    m_textRenderer.SetFont(m_engine.GetConsole()->GetFont());\n\n    loadScript();\n\n    m_audioBuffer.initialize(44100, 44100);\n    m_audioBuffer.m_writePointer = (int)(44100 * 0.1);\n\n    ysAudioParameters params;\n    params.m_bitsPerSample = 16;\n    params.m_channelCount = 1;\n    params.m_sampleRate = 44100;\n    m_outputAudioBuffer =\n        m_engine.GetAudioDevice()->CreateBuffer(&params, 44100);\n\n    m_audioSource = m_engine.GetAudioDevice()->CreateSource(m_outputAudioBuffer);\n    m_audioSource->SetMode((m_simulator->getEngine() != nullptr)\n        ? ysAudioSource::Mode::Loop\n        : ysAudioSource::Mode::Stop);\n    m_audioSource->SetPan(0.0f);\n    m_audioSource->SetVolume(1.0f);\n\n#ifdef ATG_ENGINE_SIM_DISCORD_ENABLED\n    // Create a global instance of discord-rpc\n    CDiscord::CreateInstance();\n\n    // Enable it, this needs to be set via a config file of some sort. \n    GetDiscordManager()->SetUseDiscord(true);\n    DiscordRichPresence passMe = { 0 };\n\n    std::string engineName = (m_iceEngine != nullptr)\n        ? m_iceEngine->getName()\n        : \"Broken Engine\";\n\n    GetDiscordManager()->SetStatus(passMe, engineName, s_buildVersion);\n#endif /* ATG_ENGINE_SIM_DISCORD_ENABLED */\n}\n\nvoid EngineSimApplication::process(float frame_dt) {\n    frame_dt = static_cast<float>(clamp(frame_dt, 1 / 200.0f, 1 / 30.0f));\n\n    double speed = 1.0 / 1.0;\n    if (m_engine.IsKeyDown(ysKey::Code::N1)) {\n        speed = 1 / 10.0;\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::N2)) {\n        speed = 1 / 100.0;\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::N3)) {\n        speed = 1 / 200.0;\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::N4)) {\n        speed = 1 / 500.0;\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::N5)) {\n        speed = 1 / 1000.0;\n    }\n\n    if (m_engine.IsKeyDown(ysKey::Code::F1)) {\n        m_displayAngle += frame_dt * 1.0f;\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::F2)) {\n        m_displayAngle -= frame_dt * 1.0f;\n    }\n    else if (m_engine.ProcessKeyDown(ysKey::Code::F3)) {\n        m_displayAngle = 0.0f;\n    }\n\n    m_simulator->setSimulationSpeed(speed);\n\n    const double avgFramerate = clamp(m_engine.GetAverageFramerate(), 30.0f, 1000.0f);\n    m_simulator->startFrame(1 / avgFramerate);\n\n    auto proc_t0 = std::chrono::steady_clock::now();\n    const int iterationCount = m_simulator->getFrameIterationCount();\n    while (m_simulator->simulateStep()) {\n        m_oscCluster->sample();\n    }\n\n    auto proc_t1 = std::chrono::steady_clock::now();\n\n    m_simulator->endFrame();\n\n    auto duration = proc_t1 - proc_t0;\n    if (iterationCount > 0) {\n        m_performanceCluster->addTimePerTimestepSample(\n            (duration.count() / 1E9) / iterationCount);\n    }\n\n    const SampleOffset safeWritePosition = m_audioSource->GetCurrentWritePosition();\n    const SampleOffset writePosition = m_audioBuffer.m_writePointer;\n\n    SampleOffset targetWritePosition =\n        m_audioBuffer.getBufferIndex(safeWritePosition, (int)(44100 * 0.1));\n    SampleOffset maxWrite = m_audioBuffer.offsetDelta(writePosition, targetWritePosition);\n\n    SampleOffset currentLead = m_audioBuffer.offsetDelta(safeWritePosition, writePosition);\n    SampleOffset newLead = m_audioBuffer.offsetDelta(safeWritePosition, targetWritePosition);\n\n    if (currentLead > 44100 * 0.5) {\n        m_audioBuffer.m_writePointer = m_audioBuffer.getBufferIndex(safeWritePosition, (int)(44100 * 0.05));\n        currentLead = m_audioBuffer.offsetDelta(safeWritePosition, m_audioBuffer.m_writePointer);\n        maxWrite = m_audioBuffer.offsetDelta(m_audioBuffer.m_writePointer, targetWritePosition);\n    }\n\n    if (currentLead > newLead) {\n        maxWrite = 0;\n    }\n\n    int16_t *samples = new int16_t[maxWrite];\n    const int readSamples = m_simulator->readAudioOutput(maxWrite, samples);\n\n    for (SampleOffset i = 0; i < (SampleOffset)readSamples && i < maxWrite; ++i) {\n        const int16_t sample = samples[i];\n        if (m_oscillatorSampleOffset % 4 == 0) {\n            m_oscCluster->getAudioWaveformOscilloscope()->addDataPoint(\n                m_oscillatorSampleOffset,\n                sample / (float)(INT16_MAX));\n        }\n\n        m_audioBuffer.writeSample(sample, m_audioBuffer.m_writePointer, (int)i);\n\n        m_oscillatorSampleOffset = (m_oscillatorSampleOffset + 1) % (44100 / 10);\n    }\n\n    delete[] samples;\n\n    if (readSamples > 0) {\n        SampleOffset size0, size1;\n        void *data0, *data1;\n        m_audioSource->LockBufferSegment(\n            m_audioBuffer.m_writePointer, readSamples, &data0, &size0, &data1, &size1);\n\n        m_audioBuffer.copyBuffer(\n            reinterpret_cast<int16_t *>(data0), m_audioBuffer.m_writePointer, size0);\n        m_audioBuffer.copyBuffer(\n            reinterpret_cast<int16_t *>(data1),\n            m_audioBuffer.getBufferIndex(m_audioBuffer.m_writePointer, size0),\n            size1);\n\n        m_audioSource->UnlockBufferSegments(data0, size0, data1, size1);\n        m_audioBuffer.commitBlock(readSamples);\n    }\n\n    m_performanceCluster->addInputBufferUsageSample(\n        (double)m_simulator->getSynthesizerInputLatency() / m_simulator->getSynthesizerInputLatencyTarget());\n    m_performanceCluster->addAudioLatencySample(\n        m_audioBuffer.offsetDelta(m_audioSource->GetCurrentWritePosition(), m_audioBuffer.m_writePointer) / (44100 * 0.1));\n}\n\nvoid EngineSimApplication::render() {\n    for (SimulationObject *object : m_objects) {\n        object->generateGeometry();\n    }\n\n    m_viewParameters.Sublayer = 0;\n    for (SimulationObject *object : m_objects) {\n        object->render(&getViewParameters());\n    }\n\n    m_viewParameters.Sublayer = 1;\n    for (SimulationObject *object : m_objects) {\n        object->render(&getViewParameters());\n    }\n\n    m_viewParameters.Sublayer = 2;\n    for (SimulationObject *object : m_objects) {\n        object->render(&getViewParameters());\n    }\n\n    m_uiManager.render();\n}\n\nfloat EngineSimApplication::pixelsToUnits(float pixels) const {\n    const float f = m_displayHeight / m_engineView->m_bounds.height();\n    return pixels * f;\n}\n\nfloat EngineSimApplication::unitsToPixels(float units) const {\n    const float f = m_engineView->m_bounds.height() / m_displayHeight;\n    return units * f;\n}\n\nvoid EngineSimApplication::run() {\n    while (true) {\n        m_engine.StartFrame();\n\n        if (!m_engine.IsOpen()) break;\n        if (m_engine.ProcessKeyDown(ysKey::Code::Escape)) {\n            break;\n        }\n\n        if (m_engine.ProcessKeyDown(ysKey::Code::Return)) {\n            m_audioSource->SetMode(ysAudioSource::Mode::Stop);\n            loadScript();\n            if (m_simulator->getEngine() != nullptr) {\n                m_audioSource->SetMode(ysAudioSource::Mode::Loop);\n            }\n        }\n\n        if (m_engine.ProcessKeyDown(ysKey::Code::Tab)) {\n            m_screen++;\n            if (m_screen > 2) m_screen = 0;\n        }\n\n        if (m_engine.ProcessKeyDown(ysKey::Code::F)) {\n            if (m_engine.GetGameWindow()->GetWindowStyle() != ysWindow::WindowStyle::Fullscreen) {\n                m_engine.GetGameWindow()->SetWindowStyle(ysWindow::WindowStyle::Fullscreen);\n                m_infoCluster->setLogMessage(\"Entered fullscreen mode\");\n            }\n            else {\n                m_engine.GetGameWindow()->SetWindowStyle(ysWindow::WindowStyle::Windowed);\n                m_infoCluster->setLogMessage(\"Exited fullscreen mode\");\n            }\n        }\n\n        m_gameWindowHeight = m_engine.GetGameWindow()->GetGameHeight();\n        m_screenHeight = m_engine.GetGameWindow()->GetScreenHeight();\n        m_screenWidth = m_engine.GetGameWindow()->GetScreenWidth();\n\n        updateScreenSizeStability();\n\n        processEngineInput();\n\n        if (m_engine.ProcessKeyDown(ysKey::Code::Insert) &&\n            m_engine.GetGameWindow()->IsActive()) {\n            if (!isRecording() && readyToRecord()) {\n                startRecording();\n            }\n            else if (isRecording()) {\n                stopRecording();\n            }\n        }\n\n        if (isRecording() && !readyToRecord()) {\n            stopRecording();\n        }\n\n        if (!m_paused || m_engine.ProcessKeyDown(ysKey::Code::Right)) {\n            process(m_engine.GetFrameLength());\n        }\n\n        m_uiManager.update(m_engine.GetFrameLength());\n\n        renderScene();\n\n        m_engine.EndFrame();\n\n        if (isRecording()) {\n            recordFrame();\n        }\n    }\n\n    if (isRecording()) {\n        stopRecording();\n    }\n\n    m_simulator->endAudioRenderingThread();\n}\n\nvoid EngineSimApplication::destroy() {\n    m_shaderSet.Destroy();\n\n    m_engine.GetDevice()->DestroyGPUBuffer(m_geometryVertexBuffer);\n    m_engine.GetDevice()->DestroyGPUBuffer(m_geometryIndexBuffer);\n\n    m_assetManager.Destroy();\n    m_engine.Destroy();\n\n    m_simulator->destroy();\n    m_audioBuffer.destroy();\n}\n\nvoid EngineSimApplication::loadEngine(\n    Engine *engine,\n    Vehicle *vehicle,\n    Transmission *transmission)\n{\n    destroyObjects();\n\n    if (m_simulator != nullptr) {\n        m_simulator->releaseSimulation();\n        delete m_simulator;\n    }\n\n    if (m_vehicle != nullptr) {\n        delete m_vehicle;\n        m_vehicle = nullptr;\n    }\n\n    if (m_transmission != nullptr) {\n        delete m_transmission;\n        m_transmission = nullptr;\n    }\n\n    if (m_iceEngine != nullptr) {\n        m_iceEngine->destroy();\n        delete m_iceEngine;\n    }\n\n    m_iceEngine = engine;\n    m_vehicle = vehicle;\n    m_transmission = transmission;\n\n    m_simulator = engine->createSimulator(vehicle, transmission);\n\n    if (engine == nullptr || vehicle == nullptr || transmission == nullptr) {\n        m_iceEngine = nullptr;\n        m_viewParameters.Layer1 = 0;\n\n        return;\n    }\n\n    createObjects(engine);\n\n    m_viewParameters.Layer1 = engine->getMaxDepth();\n    engine->calculateDisplacement();\n\n    m_simulator->setSimulationFrequency(engine->getSimulationFrequency());\n\n    Synthesizer::AudioParameters audioParams = m_simulator->synthesizer().getAudioParameters();\n    audioParams.inputSampleNoise = static_cast<float>(engine->getInitialJitter());\n    audioParams.airNoise = static_cast<float>(engine->getInitialNoise());\n    audioParams.dF_F_mix = static_cast<float>(engine->getInitialHighFrequencyGain());\n    m_simulator->synthesizer().setAudioParameters(audioParams);\n\n    for (int i = 0; i < engine->getExhaustSystemCount(); ++i) {\n        ImpulseResponse *response = engine->getExhaustSystem(i)->getImpulseResponse();\n\n        ysWindowsAudioWaveFile waveFile;\n        waveFile.OpenFile(response->getFilename().c_str());\n        waveFile.InitializeInternalBuffer(waveFile.GetSampleCount());\n        waveFile.FillBuffer(0);\n        waveFile.CloseFile();\n\n        m_simulator->synthesizer().initializeImpulseResponse(\n            reinterpret_cast<const int16_t *>(waveFile.GetBuffer()),\n            waveFile.GetSampleCount(),\n            response->getVolume(),\n            i\n        );\n\n        waveFile.DestroyInternalBuffer();\n    }\n\n    m_simulator->startAudioRenderingThread();\n}\n\nvoid EngineSimApplication::drawGenerated(\n    const GeometryGenerator::GeometryIndices &indices,\n    int layer)\n{\n    drawGenerated(indices, layer, m_shaders.GetRegularFlags());\n}\n\nvoid EngineSimApplication::drawGeneratedUi(\n    const GeometryGenerator::GeometryIndices &indices,\n    int layer)\n{\n    drawGenerated(indices, layer, m_shaders.GetUiFlags());\n}\n\nvoid EngineSimApplication::drawGenerated(\n    const GeometryGenerator::GeometryIndices &indices,\n    int layer,\n    dbasic::StageEnableFlags flags)\n{\n    m_engine.DrawGeneric(\n        flags,\n        m_geometryIndexBuffer,\n        m_geometryVertexBuffer,\n        sizeof(dbasic::Vertex),\n        indices.BaseIndex,\n        indices.BaseVertex,\n        indices.FaceCount,\n        false,\n        layer);\n}\n\nvoid EngineSimApplication::configure(const ApplicationSettings &settings) {\n    m_applicationSettings = settings;\n\n    if (settings.startFullscreen) {\n        m_engine.GetGameWindow()->SetWindowStyle(ysWindow::WindowStyle::Fullscreen);\n    }\n\n    m_background = ysColor::srgbiToLinear(m_applicationSettings.colorBackground);\n    m_foreground = ysColor::srgbiToLinear(m_applicationSettings.colorForeground);\n    m_shadow = ysColor::srgbiToLinear(m_applicationSettings.colorShadow);\n    m_highlight1 = ysColor::srgbiToLinear(m_applicationSettings.colorHighlight1);\n    m_highlight2 = ysColor::srgbiToLinear(m_applicationSettings.colorHighlight2);\n    m_pink = ysColor::srgbiToLinear(m_applicationSettings.colorPink);\n    m_red = ysColor::srgbiToLinear(m_applicationSettings.colorRed);\n    m_orange = ysColor::srgbiToLinear(m_applicationSettings.colorOrange);\n    m_yellow = ysColor::srgbiToLinear(m_applicationSettings.colorYellow);\n    m_blue = ysColor::srgbiToLinear(m_applicationSettings.colorBlue);\n    m_green = ysColor::srgbiToLinear(m_applicationSettings.colorGreen);\n}\n\nvoid EngineSimApplication::createObjects(Engine *engine) {\n    for (int i = 0; i < engine->getCylinderCount(); ++i) {\n        ConnectingRodObject *rodObject = new ConnectingRodObject;\n        rodObject->initialize(this);\n        rodObject->m_connectingRod = engine->getConnectingRod(i);\n        m_objects.push_back(rodObject);\n\n        PistonObject *pistonObject = new PistonObject;\n        pistonObject->initialize(this);\n        pistonObject->m_piston = engine->getPiston(i);\n        m_objects.push_back(pistonObject);\n\n        CombustionChamberObject *ccObject = new CombustionChamberObject;\n        ccObject->initialize(this);\n        ccObject->m_chamber = m_iceEngine->getChamber(i);\n        m_objects.push_back(ccObject);\n    }\n\n    for (int i = 0; i < engine->getCrankshaftCount(); ++i) {\n        CrankshaftObject *crankshaftObject = new CrankshaftObject;\n        crankshaftObject->initialize(this);\n        crankshaftObject->m_crankshaft = engine->getCrankshaft(i);\n        m_objects.push_back(crankshaftObject);\n    }\n\n    for (int i = 0; i < engine->getCylinderBankCount(); ++i) {\n        CylinderBankObject *cbObject = new CylinderBankObject;\n        cbObject->initialize(this);\n        cbObject->m_bank = engine->getCylinderBank(i);\n        cbObject->m_head = engine->getHead(i);\n        m_objects.push_back(cbObject);\n\n        CylinderHeadObject *chObject = new CylinderHeadObject;\n        chObject->initialize(this);\n        chObject->m_head = engine->getHead(i);\n        chObject->m_engine = engine;\n        m_objects.push_back(chObject);\n    }\n}\n\nvoid EngineSimApplication::destroyObjects() {\n    for (SimulationObject *object : m_objects) {\n        object->destroy();\n        delete object;\n    }\n\n    m_objects.clear();\n}\n\nconst SimulationObject::ViewParameters &\n    EngineSimApplication::getViewParameters() const\n{\n    return m_viewParameters;\n}\n\nvoid EngineSimApplication::loadScript() {\n    Engine *engine = nullptr;\n    Vehicle *vehicle = nullptr;\n    Transmission *transmission = nullptr;\n\n#ifdef ATG_ENGINE_SIM_PIRANHA_ENABLED\n    es_script::Compiler compiler;\n    compiler.initialize();\n    const bool compiled = compiler.compile(\"../assets/main.mr\");\n    if (compiled) {\n        const es_script::Compiler::Output output = compiler.execute();\n        configure(output.applicationSettings);\n\n        engine = output.engine;\n        vehicle = output.vehicle;\n        transmission = output.transmission;\n    }\n    else {\n        engine = nullptr;\n        vehicle = nullptr;\n        transmission = nullptr;\n    }\n\n    compiler.destroy();\n#endif /* ATG_ENGINE_SIM_PIRANHA_ENABLED */\n\n    if (vehicle == nullptr) {\n        Vehicle::Parameters vehParams;\n        vehParams.mass = units::mass(1597, units::kg);\n        vehParams.diffRatio = 3.42;\n        vehParams.tireRadius = units::distance(10, units::inch);\n        vehParams.dragCoefficient = 0.25;\n        vehParams.crossSectionArea = units::distance(6.0, units::foot) * units::distance(6.0, units::foot);\n        vehParams.rollingResistance = 2000.0;\n        vehicle = new Vehicle;\n        vehicle->initialize(vehParams);\n    }\n\n    if (transmission == nullptr) {\n        const double gearRatios[] = { 2.97, 2.07, 1.43, 1.00, 0.84, 0.56 };\n        Transmission::Parameters tParams;\n        tParams.GearCount = 6;\n        tParams.GearRatios = gearRatios;\n        tParams.MaxClutchTorque = units::torque(1000.0, units::ft_lb);\n        transmission = new Transmission;\n        transmission->initialize(tParams);\n    }\n\n    loadEngine(engine, vehicle, transmission);\n    refreshUserInterface();\n}\n\nvoid EngineSimApplication::processEngineInput() {\n    if (m_iceEngine == nullptr) {\n        return;\n    }\n\n    const float dt = m_engine.GetFrameLength();\n    const bool fineControlMode = m_engine.IsKeyDown(ysKey::Code::Space);\n\n    const int mouseWheel = m_engine.GetMouseWheel();\n    const int mouseWheelDelta = mouseWheel - m_lastMouseWheel;\n    m_lastMouseWheel = mouseWheel;\n\n    bool fineControlInUse = false;\n    if (m_engine.IsKeyDown(ysKey::Code::Z)) {\n        const double rate = fineControlMode\n            ? 0.001\n            : 0.01;\n\n        Synthesizer::AudioParameters audioParams = m_simulator->synthesizer().getAudioParameters();\n        audioParams.volume = clamp(audioParams.volume + mouseWheelDelta * rate * dt);\n\n        m_simulator->synthesizer().setAudioParameters(audioParams);\n        fineControlInUse = true;\n\n        m_infoCluster->setLogMessage(\"[Z] - Set volume to \" + std::to_string(audioParams.volume));\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::X)) {\n        const double rate = fineControlMode\n            ? 0.001\n            : 0.01;\n\n        Synthesizer::AudioParameters audioParams = m_simulator->synthesizer().getAudioParameters();\n        audioParams.convolution = clamp(audioParams.convolution + mouseWheelDelta * rate * dt);\n\n        m_simulator->synthesizer().setAudioParameters(audioParams);\n        fineControlInUse = true;\n\n        m_infoCluster->setLogMessage(\"[X] - Set convolution level to \" + std::to_string(audioParams.convolution));\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::C)) {\n        const double rate = fineControlMode\n            ? 0.00001\n            : 0.001;\n\n        Synthesizer::AudioParameters audioParams = m_simulator->synthesizer().getAudioParameters();\n        audioParams.dF_F_mix = clamp(audioParams.dF_F_mix + mouseWheelDelta * rate * dt);\n\n        m_simulator->synthesizer().setAudioParameters(audioParams);\n        fineControlInUse = true;\n\n        m_infoCluster->setLogMessage(\"[C] - Set high freq. gain to \" + std::to_string(audioParams.dF_F_mix));\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::V)) {\n        const double rate = fineControlMode\n            ? 0.001\n            : 0.01;\n\n        Synthesizer::AudioParameters audioParams = m_simulator->synthesizer().getAudioParameters();\n        audioParams.airNoise = clamp(audioParams.airNoise + mouseWheelDelta * rate * dt);\n\n        m_simulator->synthesizer().setAudioParameters(audioParams);\n        fineControlInUse = true;\n\n        m_infoCluster->setLogMessage(\"[V] - Set low freq. noise to \" + std::to_string(audioParams.airNoise));\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::B)) {\n        const double rate = fineControlMode\n            ? 0.001\n            : 0.01;\n\n        Synthesizer::AudioParameters audioParams = m_simulator->synthesizer().getAudioParameters();\n        audioParams.inputSampleNoise = clamp(audioParams.inputSampleNoise + mouseWheelDelta * rate * dt);\n\n        m_simulator->synthesizer().setAudioParameters(audioParams);\n        fineControlInUse = true;\n\n        m_infoCluster->setLogMessage(\"[B] - Set high freq. noise to \" + std::to_string(audioParams.inputSampleNoise));\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::N)) {\n        const double rate = fineControlMode\n            ? 10.0\n            : 100.0;\n\n        const double newSimulationFrequency = clamp(\n            m_simulator->getSimulationFrequency() + mouseWheelDelta * rate * dt,\n            400.0, 400000.0);\n\n        m_simulator->setSimulationFrequency(newSimulationFrequency);\n        fineControlInUse = true;\n\n        m_infoCluster->setLogMessage(\"[N] - Set simulation freq to \" + std::to_string(m_simulator->getSimulationFrequency()));\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::G) && m_simulator->m_dyno.m_hold) {\n        if (mouseWheelDelta > 0) {\n            m_dynoSpeed += m_iceEngine->getDynoHoldStep();\n        }\n        else if (mouseWheelDelta < 0) {\n            m_dynoSpeed -= m_iceEngine->getDynoHoldStep();\n        }\n\n        m_dynoSpeed = clamp(m_dynoSpeed, m_iceEngine->getDynoMinSpeed(), m_iceEngine->getDynoMaxSpeed());\n\n        m_infoCluster->setLogMessage(\"[G] - Set dyno speed to \" + std::to_string(units::toRpm(m_dynoSpeed)));\n        fineControlInUse = true;\n    }\n\n    const double prevTargetThrottle = m_targetSpeedSetting;\n    m_targetSpeedSetting = fineControlMode ? m_targetSpeedSetting : 0.0;\n    if (m_engine.IsKeyDown(ysKey::Code::Q)) {\n        m_targetSpeedSetting = 0.01;\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::W)) {\n        m_targetSpeedSetting = 0.1;\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::E)) {\n        m_targetSpeedSetting = 0.2;\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::R)) {\n        m_targetSpeedSetting = 1.0;\n    }\n    else if (fineControlMode && !fineControlInUse) {\n        m_targetSpeedSetting = clamp(m_targetSpeedSetting + mouseWheelDelta * 0.0001);\n    }\n\n    if (prevTargetThrottle != m_targetSpeedSetting) {\n        m_infoCluster->setLogMessage(\"Speed control set to \" + std::to_string(m_targetSpeedSetting));\n    }\n\n    m_speedSetting = m_targetSpeedSetting * 0.5 + 0.5 * m_speedSetting;\n\n    m_iceEngine->setSpeedControl(m_speedSetting);\n    if (m_engine.ProcessKeyDown(ysKey::Code::M)) {\n        const int currentLayer = getViewParameters().Layer0;\n        if (currentLayer + 1 < m_iceEngine->getMaxDepth()) {\n            setViewLayer(currentLayer + 1);\n        }\n\n        m_infoCluster->setLogMessage(\"[M] - Set render layer to \" + std::to_string(getViewParameters().Layer0));\n    }\n\n    if (m_engine.ProcessKeyDown(ysKey::Code::OEM_Comma)) {\n        if (getViewParameters().Layer0 - 1 >= 0)\n            setViewLayer(getViewParameters().Layer0 - 1);\n\n        m_infoCluster->setLogMessage(\"[,] - Set render layer to \" + std::to_string(getViewParameters().Layer0));\n    }\n\n    if (m_engine.ProcessKeyDown(ysKey::Code::D)) {\n        m_simulator->m_dyno.m_enabled = !m_simulator->m_dyno.m_enabled;\n\n        const std::string msg = m_simulator->m_dyno.m_enabled\n            ? \"DYNOMOMETER ENABLED\"\n            : \"DYNOMOMETER DISABLED\";\n        m_infoCluster->setLogMessage(msg);\n    }\n\n    if (m_engine.ProcessKeyDown(ysKey::Code::H)) {\n        m_simulator->m_dyno.m_hold = !m_simulator->m_dyno.m_hold;\n\n        const std::string msg = m_simulator->m_dyno.m_hold\n            ? m_simulator->m_dyno.m_enabled ? \"HOLD ENABLED\" : \"HOLD ON STANDBY [ENABLE DYNO. FOR HOLD]\"\n            : \"HOLD DISABLED\";\n        m_infoCluster->setLogMessage(msg);\n    }\n\n    if (m_simulator->m_dyno.m_enabled) {\n        if (!m_simulator->m_dyno.m_hold) {\n            if (m_simulator->getFilteredDynoTorque() > units::torque(1.0, units::ft_lb)) {\n                m_dynoSpeed += units::rpm(500) * dt;\n            }\n            else {\n                m_dynoSpeed *= (1 / (1 + dt));\n            }\n\n            if (m_dynoSpeed > m_iceEngine->getRedline()) {\n                m_simulator->m_dyno.m_enabled = false;\n                m_dynoSpeed = units::rpm(0);\n            }\n        }\n    }\n    else {\n        if (!m_simulator->m_dyno.m_hold) {\n            m_dynoSpeed = units::rpm(0);\n        }\n    }\n\n    m_dynoSpeed = clamp(m_dynoSpeed, m_iceEngine->getDynoMinSpeed(), m_iceEngine->getDynoMaxSpeed());\n    m_simulator->m_dyno.m_rotationSpeed = m_dynoSpeed;\n\n    const bool prevStarterEnabled = m_simulator->m_starterMotor.m_enabled;\n    if (m_engine.IsKeyDown(ysKey::Code::S)) {\n        m_simulator->m_starterMotor.m_enabled = true;\n    }\n    else {\n        m_simulator->m_starterMotor.m_enabled = false;\n    }\n\n    if (prevStarterEnabled != m_simulator->m_starterMotor.m_enabled) {\n        const std::string msg = m_simulator->m_starterMotor.m_enabled\n            ? \"STARTER ENABLED\"\n            : \"STARTER DISABLED\";\n        m_infoCluster->setLogMessage(msg);\n    }\n\n    if (m_engine.ProcessKeyDown(ysKey::Code::A)) {\n        m_simulator->getEngine()->getIgnitionModule()->m_enabled =\n            !m_simulator->getEngine()->getIgnitionModule()->m_enabled;\n\n        const std::string msg = m_simulator->getEngine()->getIgnitionModule()->m_enabled\n            ? \"IGNITION ENABLED\"\n            : \"IGNITION DISABLED\";\n        m_infoCluster->setLogMessage(msg);\n    }\n\n    if (m_engine.ProcessKeyDown(ysKey::Code::Up)) {\n        m_simulator->getTransmission()->changeGear(m_simulator->getTransmission()->getGear() + 1);\n\n        m_infoCluster->setLogMessage(\n            \"UPSHIFTED TO \" + std::to_string(m_simulator->getTransmission()->getGear() + 1));\n    }\n    else if (m_engine.ProcessKeyDown(ysKey::Code::Down)) {\n        m_simulator->getTransmission()->changeGear(m_simulator->getTransmission()->getGear() - 1);\n\n        if (m_simulator->getTransmission()->getGear() != -1) {\n            m_infoCluster->setLogMessage(\n                \"DOWNSHIFTED TO \" + std::to_string(m_simulator->getTransmission()->getGear() + 1));\n        }\n        else {\n            m_infoCluster->setLogMessage(\"SHIFTED TO NEUTRAL\");\n        }\n    }\n\n    if (m_engine.IsKeyDown(ysKey::Code::T)) {\n        m_targetClutchPressure -= 0.2 * dt;\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::U)) {\n        m_targetClutchPressure += 0.2 * dt;\n    }\n    else if (m_engine.IsKeyDown(ysKey::Code::Shift)) {\n        m_targetClutchPressure = 0.0;\n        m_infoCluster->setLogMessage(\"CLUTCH DEPRESSED\");\n    }\n    else if (!m_engine.IsKeyDown(ysKey::Code::Y)) {\n        m_targetClutchPressure = 1.0;\n    }\n\n    m_targetClutchPressure = clamp(m_targetClutchPressure);\n\n    double clutchRC = 0.001;\n    if (m_engine.IsKeyDown(ysKey::Code::Space)) {\n        clutchRC = 1.0;\n    }\n\n    const double clutch_s = dt / (dt + clutchRC);\n    m_clutchPressure = m_clutchPressure * (1 - clutch_s) + m_targetClutchPressure * clutch_s;\n    m_simulator->getTransmission()->setClutchPressure(m_clutchPressure);\n}\n\nvoid EngineSimApplication::renderScene() {\n    getShaders()->ResetBaseColor();\n    getShaders()->SetObjectTransform(ysMath::LoadIdentity());\n\n    m_textRenderer.SetColor(ysColor::linearToSrgb(m_foreground));\n    m_shaders.SetClearColor(ysColor::linearToSrgb(m_shadow));\n\n    const int screenWidth = m_engine.GetGameWindow()->GetGameWidth();\n    const int screenHeight = m_engine.GetGameWindow()->GetGameHeight();\n    const float aspectRatio = screenWidth / (float)screenHeight;\n\n    const Point cameraPos = m_engineView->getCameraPosition();\n    m_shaders.m_cameraPosition = ysMath::LoadVector(cameraPos.x, cameraPos.y);\n\n    m_shaders.CalculateUiCamera(screenWidth, screenHeight);\n\n    if (m_screen == 0) {\n        Bounds windowBounds((float)screenWidth, (float)screenHeight, { 0, (float)screenHeight });\n        Grid grid;\n        grid.v_cells = 2;\n        grid.h_cells = 3;\n        Grid grid3x3;\n        grid3x3.v_cells = 3;\n        grid3x3.h_cells = 3;\n        m_engineView->setDrawFrame(true);\n        m_engineView->setBounds(grid.get(windowBounds, 1, 0, 1, 1));\n        m_engineView->setLocalPosition({ 0, 0 });\n\n        m_rightGaugeCluster->m_bounds = grid.get(windowBounds, 2, 0, 1, 2);\n        m_oscCluster->m_bounds = grid.get(windowBounds, 1, 1);\n        m_performanceCluster->m_bounds = grid3x3.get(windowBounds, 0, 1);\n        m_loadSimulationCluster->m_bounds = grid3x3.get(windowBounds, 0, 2);\n\n        Grid grid1x3;\n        grid1x3.v_cells = 3;\n        grid1x3.h_cells = 1;\n        m_mixerCluster->m_bounds = grid1x3.get(grid3x3.get(windowBounds, 0, 0), 0, 2);\n        m_infoCluster->m_bounds = grid1x3.get(grid3x3.get(windowBounds, 0, 0), 0, 0, 1, 2);\n\n        m_engineView->setVisible(true);\n        m_rightGaugeCluster->setVisible(true);\n        m_oscCluster->setVisible(true);\n        m_performanceCluster->setVisible(true);\n        m_loadSimulationCluster->setVisible(true);\n        m_mixerCluster->setVisible(true);\n        m_infoCluster->setVisible(true);\n\n        m_oscCluster->activate();\n    }\n    else if (m_screen == 1) {\n        Bounds windowBounds((float)screenWidth, (float)screenHeight, { 0, (float)screenHeight });\n        m_engineView->setDrawFrame(false);\n        m_engineView->setBounds(windowBounds);\n        m_engineView->setLocalPosition({ 0, 0 });\n        m_engineView->activate();\n\n        m_engineView->setVisible(true);\n        m_rightGaugeCluster->setVisible(false);\n        m_oscCluster->setVisible(false);\n        m_performanceCluster->setVisible(false);\n        m_loadSimulationCluster->setVisible(false);\n        m_mixerCluster->setVisible(false);\n        m_infoCluster->setVisible(false);\n    }\n    else if (m_screen == 2) {\n        Bounds windowBounds((float)screenWidth, (float)screenHeight, { 0, (float)screenHeight });\n        Grid grid;\n        grid.v_cells = 1;\n        grid.h_cells = 3;\n        m_engineView->setDrawFrame(true);\n        m_engineView->setBounds(grid.get(windowBounds, 0, 0, 2, 1));\n        m_engineView->setLocalPosition({ 0, 0 });\n        m_engineView->activate();\n\n        m_rightGaugeCluster->m_bounds = grid.get(windowBounds, 2, 0, 1, 1);\n\n        m_engineView->setVisible(true);\n        m_rightGaugeCluster->setVisible(true);\n        m_oscCluster->setVisible(false);\n        m_performanceCluster->setVisible(false);\n        m_loadSimulationCluster->setVisible(false);\n        m_mixerCluster->setVisible(false);\n        m_infoCluster->setVisible(false);\n    }\n\n    const float cameraAspectRatio =\n        m_engineView->m_bounds.width() / m_engineView->m_bounds.height();\n    m_engine.GetDevice()->ResizeRenderTarget(\n        m_mainRenderTarget,\n        m_engineView->m_bounds.width(),\n        m_engineView->m_bounds.height(),\n        m_engineView->m_bounds.width(),\n        m_engineView->m_bounds.height()\n    );\n    m_engine.GetDevice()->RepositionRenderTarget(\n        m_mainRenderTarget,\n        m_engineView->m_bounds.getPosition(Bounds::tl).x,\n        screenHeight - m_engineView->m_bounds.getPosition(Bounds::tl).y\n    );\n    m_shaders.CalculateCamera(\n        cameraAspectRatio * m_displayHeight / m_engineView->m_zoom,\n        m_displayHeight / m_engineView->m_zoom,\n        m_engineView->m_bounds,\n        m_screenWidth,\n        m_screenHeight,\n        m_displayAngle);\n\n    m_geometryGenerator.reset();\n\n    render();\n\n    m_engine.GetDevice()->EditBufferDataRange(\n        m_geometryVertexBuffer,\n        (char *)m_geometryGenerator.getVertexData(),\n        sizeof(dbasic::Vertex) * m_geometryGenerator.getCurrentVertexCount(),\n        0);\n\n    m_engine.GetDevice()->EditBufferDataRange(\n        m_geometryIndexBuffer,\n        (char *)m_geometryGenerator.getIndexData(),\n        sizeof(unsigned short) * m_geometryGenerator.getCurrentIndexCount(),\n        0);\n}\n\nvoid EngineSimApplication::refreshUserInterface() {\n    m_uiManager.destroy();\n    m_uiManager.initialize(this);\n\n    m_engineView = m_uiManager.getRoot()->addElement<EngineView>();\n    m_rightGaugeCluster = m_uiManager.getRoot()->addElement<RightGaugeCluster>();\n    m_oscCluster = m_uiManager.getRoot()->addElement<OscilloscopeCluster>();\n    m_performanceCluster = m_uiManager.getRoot()->addElement<PerformanceCluster>();\n    m_loadSimulationCluster = m_uiManager.getRoot()->addElement<LoadSimulationCluster>();\n    m_mixerCluster = m_uiManager.getRoot()->addElement<MixerCluster>();\n    m_infoCluster = m_uiManager.getRoot()->addElement<InfoCluster>();\n\n    m_infoCluster->setEngine(m_iceEngine);\n    m_rightGaugeCluster->m_simulator = m_simulator;\n    m_rightGaugeCluster->setEngine(m_iceEngine);\n    m_oscCluster->setSimulator(m_simulator);\n    if (m_iceEngine != nullptr) {\n        m_oscCluster->setDynoMaxRange(units::toRpm(m_iceEngine->getRedline()));\n    }\n    m_performanceCluster->setSimulator(m_simulator);\n    m_loadSimulationCluster->setSimulator(m_simulator);\n    m_mixerCluster->setSimulator(m_simulator);\n}\n\nvoid EngineSimApplication::startRecording() {\n    m_recording = true;\n\n#ifdef ATG_ENGINE_SIM_VIDEO_CAPTURE\n    atg_dtv::Encoder::VideoSettings settings{};\n\n    // Output filename\n    settings.fname = \"../workspace/video_capture/engine_sim_video_capture.mp4\";\n    settings.inputWidth = m_engine.GetScreenWidth();\n    settings.inputHeight = m_engine.GetScreenHeight();\n    settings.width = settings.inputWidth;\n    settings.height = settings.inputHeight;\n    settings.hardwareEncoding = true;\n    settings.inputAlpha = true;\n    settings.bitRate = 40000000;\n\n    m_encoder.run(settings, 2);\n#endif /* ATG_ENGINE_SIM_VIDEO_CAPTURE */\n}\n\nvoid EngineSimApplication::updateScreenSizeStability() {\n    m_screenResolution[m_screenResolutionIndex][0] = m_engine.GetScreenWidth();\n    m_screenResolution[m_screenResolutionIndex][1] = m_engine.GetScreenHeight();\n\n    m_screenResolutionIndex = (m_screenResolutionIndex + 1) % ScreenResolutionHistoryLength;\n}\n\nbool EngineSimApplication::readyToRecord() {\n    const int w = m_screenResolution[0][0];\n    const int h = m_screenResolution[0][1];\n\n    if (w <= 0 && h <= 0) return false;\n    if ((w % 2) != 0 || (h % 2) != 0) return false;\n\n    for (int i = 1; i < ScreenResolutionHistoryLength; ++i) {\n        if (m_screenResolution[i][0] != w) return false;\n        if (m_screenResolution[i][1] != h) return false;\n    }\n\n    return true;\n}\n\nvoid EngineSimApplication::stopRecording() {\n    m_recording = false;\n\n#ifdef ATG_ENGINE_SIM_VIDEO_CAPTURE\n    m_encoder.commit();\n    m_encoder.stop();\n#endif /* ATG_ENGINE_SIM_VIDEO_CAPTURE */\n}\n\nvoid EngineSimApplication::recordFrame() {\n#ifdef ATG_ENGINE_SIM_VIDEO_CAPTURE\n    atg_dtv::Frame *frame = m_encoder.newFrame(false);\n    if (frame != nullptr && m_encoder.getError() == atg_dtv::Encoder::Error::None) {\n        m_engine.GetDevice()->ReadRenderTarget(m_engine.GetScreenRenderTarget(), frame->m_rgb);\n    }\n\n    m_encoder.submitFrame();\n#endif /* ATG_ENGINE_SIM_VIDEO_CAPTURE */\n}\n"
  },
  {
    "path": "src/engine_view.cpp",
    "content": "#include \"../include/engine_view.h\"\n\n#include \"../include/engine_sim_application.h\"\n\nEngineView::EngineView() {\n    m_pan = { 0, units::distance(-6, units::inch) };\n    m_checkMouse = true;\n    m_lastScroll = 0;\n    m_zoom = 1.0f;\n    m_drawFrame = true;\n}\n\nEngineView::~EngineView() {\n    /* void */\n}\n\nvoid EngineView::update(float dt) {\n    m_mouseBounds = m_bounds;\n}\n\nvoid EngineView::render() {\n    if (m_drawFrame) {\n        drawFrame(m_bounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor(), false);\n    }\n}\n\nvoid EngineView::onMouseDown(const Point &mouseLocal) {\n    UiElement::onMouseDown(mouseLocal);\n    m_dragStart = m_pan;\n}\n\nvoid EngineView::onDrag(const Point &p0, const Point &mouse0, const Point &mouse) {\n    const Point delta = mouse - mouse0;\n    const Point deltaUnits = {\n        m_app->pixelsToUnits(delta.x),\n        m_app->pixelsToUnits(delta.y)\n    };\n\n    m_pan = m_dragStart + deltaUnits;\n}\n\nvoid EngineView::onMouseScroll(int scroll) {\n    const float f = std::powf(2.0, (float)scroll / 500.0f);\n\n    const Point prevCenter = getCenter();\n\n    m_zoom *= f;\n    const Point newCenter = getCenter();\n\n    Point diff = newCenter - prevCenter;\n    m_pan += diff * m_zoom;\n    m_dragStart += diff * m_zoom;\n}\n\nvoid EngineView::setBounds(const Bounds &bounds) {\n    m_bounds = bounds;\n}\n\nPoint EngineView::getCenter() const {\n    return getCameraPosition();\n}\n\nPoint EngineView::getCameraPosition() const {\n    return Point(-m_pan / m_zoom);\n}\n"
  },
  {
    "path": "src/exhaust_system.cpp",
    "content": "#include \"../include/exhaust_system.h\"\n\n#include \"../include/units.h\"\n\nExhaustSystem::ExhaustSystem() {\n    m_primaryFlowRate = 0;\n    m_outletFlowRate = 0;\n    m_collectorCrossSectionArea = 0;\n    m_length = 0;\n    m_primaryTubeLength = 0;\n    m_audioVolume = 0;\n    m_velocityDecay = 0;\n    m_flow = 0;\n    m_index = -1;\n    m_impulseResponse = nullptr;\n}\n\nExhaustSystem::~ExhaustSystem() {\n    /* void */\n}\n\nvoid ExhaustSystem::initialize(const Parameters &params) {\n    const double systemWidth = std::sqrt(params.collectorCrossSectionArea);\n    const double volume = params.collectorCrossSectionArea * params.length;\n    const double systemLength = params.length;\n    m_system.initialize(\n            units::pressure(1.0, units::atm),\n            volume,\n            units::celcius(25.0));\n    m_system.setGeometry(\n        systemLength,\n        systemWidth,\n        1.0,\n        0.0);\n\n    m_atmosphere.initialize(\n        units::pressure(1.0, units::atm),\n        units::volume(1000.0, units::m3),\n        units::celcius(25.0));\n    m_atmosphere.setGeometry(\n        units::distance(10.0, units::m),\n        units::distance(10.0, units::m),\n        1.0,\n        0.0);\n\n    m_primaryFlowRate = params.primaryFlowRate;\n    m_audioVolume = params.audioVolume;\n    m_outletFlowRate = params.outletFlowRate;\n    m_collectorCrossSectionArea = params.collectorCrossSectionArea;\n    m_velocityDecay = params.velocityDecay;\n    m_impulseResponse = params.impulseResponse;\n    m_length = params.length;\n    m_primaryTubeLength = params.primaryTubeLength;\n}\n\nvoid ExhaustSystem::destroy() {\n    /* void */\n}\n\nvoid ExhaustSystem::process(double dt) {\n    GasSystem::Mix airMix;\n    airMix.p_fuel = 0;\n    airMix.p_inert = 1.0;\n    airMix.p_o2 = 0.0;\n\n    m_atmosphere.reset(units::pressure(1.0, units::atm), units::celcius(25.0), airMix);\n    GasSystem::FlowParameters flowParams;\n    flowParams.crossSectionArea_0 = m_collectorCrossSectionArea;\n    flowParams.crossSectionArea_1 = units::area(10, units::m2);\n    flowParams.direction_x = 1.0;\n    flowParams.direction_y = 0.0;\n    flowParams.dt = dt;\n    flowParams.system_0 = &m_atmosphere;\n    flowParams.system_1 = &m_system;\n    flowParams.k_flow = m_outletFlowRate;\n\n    m_flow = m_system.flow(flowParams);\n\n    m_system.dissipateExcessVelocity();\n    m_system.updateVelocity(dt, m_velocityDecay);\n}\n"
  },
  {
    "path": "src/feedback_comb_filter.cpp",
    "content": "#include \"../include/feedback_comb_filter.h\"\n\n#include <assert.h>\n\nFeedbackCombFilter::FeedbackCombFilter() {\n    M = 0;\n    a_M = 1.0;\n    m_y = nullptr;\n    m_offset = 0;\n}\n\nFeedbackCombFilter::~FeedbackCombFilter() {\n    assert(m_y == nullptr);\n}\n\nvoid FeedbackCombFilter::initialize(int M) {\n    this->M = M;\n    m_y = new float[M];\n    m_offset = 0;\n}\n\nfloat FeedbackCombFilter::f(float sample) {\n    const float y_n_min_M = m_y[m_offset];\n\n    const float y_n = sample + a_M * y_n_min_M;\n\n    m_y[m_offset] = y_n;\n    m_offset = (m_offset + 1) % M;\n\n    return y_n;\n}\n\nvoid FeedbackCombFilter::destroy() {\n    delete[] m_y;\n\n    m_y = nullptr;\n}\n"
  },
  {
    "path": "src/filter.cpp",
    "content": "#include \"../include/filter.h\"\n\nFilter::Filter() {\n    /* void */\n}\n\nFilter::~Filter() {\n    /* void */\n}\n\nfloat Filter::f(float sample) {\n    return sample;\n}\n\nvoid Filter::destroy() {\n    /* void */\n}\n"
  },
  {
    "path": "src/firing_order_display.cpp",
    "content": "#include \"../include/firing_order_display.h\"\n\n#include \"../include/units.h\"\n#include \"../include/gauge.h\"\n#include \"../include/constants.h\"\n#include \"../include/engine_sim_application.h\"\n#include \"../include/ui_utilities.h\"\n\n#include <sstream>\n\n#undef min\n\nFiringOrderDisplay::FiringOrderDisplay() {\n    m_engine = nullptr;\n    m_cylinderCount = 0;\n    m_cylinderLit = nullptr;\n}\n\nFiringOrderDisplay::~FiringOrderDisplay() {\n    /* void */\n}\n\nvoid FiringOrderDisplay::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n}\n\nvoid FiringOrderDisplay::destroy() {\n    UiElement::destroy();\n}\n\nvoid FiringOrderDisplay::update(float dt) {\n    UiElement::update(dt);\n\n    if (m_engine != nullptr) {\n        if (m_engine->getCylinderCount() != m_cylinderCount) {\n            if (m_cylinderLit != nullptr) {\n                delete[] m_cylinderLit;\n            }\n\n            m_cylinderCount = m_engine->getCylinderCount();\n            m_cylinderLit = new float[m_cylinderCount];\n            memset(m_cylinderLit, 0, sizeof(float) * m_cylinderCount);\n        }\n\n        for (int i = 0; i < m_cylinderCount; ++i) {\n            if (m_engine->getChamber(i)->popLitLastFrame() || m_engine->getChamber(i)->isLit()) {\n                m_cylinderLit[i] = 0.05f + 0.95f * m_cylinderLit[i];\n            }\n            else {\n                m_cylinderLit[i] *= (dt / (dt + 0.01f));\n            }\n        }\n    }\n}\n\nvoid FiringOrderDisplay::render() {\n    drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor());\n\n    const Bounds title = m_bounds.verticalSplit(1.0f, 0.9f);\n    const Bounds body = m_bounds.verticalSplit(0.0f, 0.9f);\n\n    drawCenteredText(\"Ignition\", title.inset(20.0f), 24.0f);\n\n    const int banks = (m_engine == nullptr)\n        ? 0\n        : m_engine->getCylinderBankCount();\n\n    Grid grid;\n    grid.h_cells = banks;\n    grid.v_cells = 1;\n\n    GeometryGenerator *generator = m_app->getGeometryGenerator();\n\n    const ysVector background = m_app->getBackgroundColor();\n    const ysVector hot = mix(background, m_app->getForegroundColor(), 1.0f);\n    const ysVector fixed = mix(background, m_app->getForegroundColor(), 0.02f);\n    const ysVector cold = mix(background, m_app->getForegroundColor(), 0.001f);\n\n    std::vector<CylinderBank *> orderedBanks;\n    std::map<CylinderBank *, int> bankToIndex;\n    for (int i = 0; i < banks; ++i) {\n        orderedBanks.push_back(m_engine->getCylinderBank(i));\n    }\n\n    std::sort(\n        orderedBanks.begin(),\n        orderedBanks.end(),\n        [](CylinderBank *a, CylinderBank *b) {\n            return a->getAngle() < b->getAngle();\n        });\n    for (int i = 0; i < banks; ++i) {\n        bankToIndex[orderedBanks[i]] = i;\n    }\n\n    if (m_engine != nullptr) {\n        for (int i = 0; i < m_engine->getCylinderCount(); ++i) {\n            Piston *piston = m_engine->getPiston(i);\n            CombustionChamber *chamber = m_engine->getChamber(i);\n            CylinderBank *bank = piston->getCylinderBank();\n            const int bankIndex = bankToIndex[bank];\n            const double lit = m_cylinderLit[i];\n\n            const Bounds &b = grid.get(body, banks - bankIndex - 1, 0);\n\n            Grid bankGrid = { 1, bank->getCylinderCount() };\n            const Bounds &b_cyl =\n                bankGrid.get(\n                    b,\n                    0,\n                    bank->getCylinderCount() - piston->getCylinderIndex() - 1).inset(5.0f);\n\n            const double temperature = chamber->m_system.temperature();\n\n            const Bounds worldBounds = getRenderBounds(b_cyl);\n            const Point position = worldBounds.getPosition(Bounds::center);\n\n            const float radius = std::min(worldBounds.height() / 2, worldBounds.width() / 2);\n\n            GeometryGenerator::Circle2dParameters params;\n            params.center_x = position.x;\n            params.center_y = position.y;\n            params.radius = radius * 0.75f;\n            params.maxEdgeLength = pixelsToUnits(5.0f);\n\n            GeometryGenerator::Ring2dParameters ringParams;\n            ringParams.center_x = position.x;\n            ringParams.center_y = position.y;\n            ringParams.innerRadius = radius * 0.8f;\n            ringParams.outerRadius = radius * 0.85f;\n            ringParams.maxEdgeLength = pixelsToUnits(5.0f);\n\n            GeometryGenerator::GeometryIndices ring, light;\n            generator->startShape();\n            generator->generateRing2d(ringParams);\n            generator->endShape(&ring);\n\n            generator->startShape();\n            generator->generateCircle2d(params);\n            generator->endShape(&light);\n\n            m_app->getShaders()->SetBaseColor(fixed);\n            m_app->drawGenerated(ring, 0x11, m_app->getShaders()->GetUiFlags());\n\n            m_app->getShaders()->SetBaseColor(mix(cold, hot, (float)lit));\n            m_app->drawGenerated(light, 0x11, m_app->getShaders()->GetUiFlags());\n        }\n    }\n\n    UiElement::render();\n}\n"
  },
  {
    "path": "src/fuel.cpp",
    "content": "#include \"../include/fuel.h\"\n\n#include \"../include/units.h\"\n\n#include <cmath>\n\nFuel::Fuel() {\n    m_molecularMass = 0.0;\n    m_energyDensity = 0.0;\n    m_density = 0.0;\n    m_turbulenceToFlameSpeedRatio = nullptr;\n    m_molecularAfr = 0.0;\n    m_maxBurningEfficiency = 0.0;\n    m_maxDilutionEffect = 0.0;\n    m_maxTurbulenceEffect = 0.0;\n    m_burningEfficiencyRandomness = 0.0;\n    m_lowEfficiencyAttenuation = 0.0;\n}\n\nFuel::~Fuel() {\n    /* void */\n}\n\nvoid Fuel::initialize(const Parameters &params) {\n    m_molecularMass = params.molecularMass;\n    m_energyDensity = params.energyDensity;\n    m_density = params.density;\n    m_turbulenceToFlameSpeedRatio = params.turbulenceToFlameSpeedRatio;\n    m_molecularAfr = params.molecularAfr;\n    m_burningEfficiencyRandomness = params.burningEfficiencyRandomness;\n    m_maxBurningEfficiency = params.maxBurningEfficiency;\n    m_maxDilutionEffect = params.maxDilutionEffect;\n    m_maxTurbulenceEffect = params.maxTurbulenceEffect;\n    m_lowEfficiencyAttenuation = params.lowEfficiencyAttenuation;\n}\n\ndouble Fuel::flameSpeed(\n    double turbulence,\n    double molecularAfr,\n    double T,\n    double P,\n    double firingPressure,\n    double motoringPressure) const\n{\n    const double S_L = laminarBurningVelocity(molecularAfr, T, P);\n    const double p_adjustment = 1.0;\n\n    return m_turbulenceToFlameSpeedRatio->sampleTriangle((turbulence / S_L) * p_adjustment) * S_L;\n}\n\ndouble Fuel::laminarBurningVelocity(double molecularAfr, double T, double P) const {\n    // Assuming fuel is gasoline\n    constexpr double er_m = 1.21;\n    constexpr double B_m = units::distance(30.5, units::cm) / units::sec;\n    constexpr double B_er = -units::distance(54.9, units::cm) / units::sec;\n    const double er = molecularAfr / m_molecularAfr;\n    const double alpha = 2.4 - 0.271 * std::pow(er, 3.51);\n    const double beta = -0.357 + 0.14 * std::pow(er, 2.77);\n\n    const double S_L_0 = B_m + B_er * (er - er_m) * (er - er_m);\n    const double T_ratio = T / units::kelvin(298);\n    const double P_ratio = P / units::pressure(1.0, units::atm);\n\n    return S_L_0 * std::pow(T_ratio, alpha) * std::pow(P_ratio, beta);\n}\n"
  },
  {
    "path": "src/fuel_cluster.cpp",
    "content": "#include \"../include/fuel_cluster.h\"\n\n#include \"../include/engine_sim_application.h\"\n\n#include <sstream>\n#include <iomanip>\n\nFuelCluster::FuelCluster() {\n    m_engine = nullptr;\n    m_simulator = nullptr;\n}\n\nFuelCluster::~FuelCluster() {\n    /* void */\n}\n\nvoid FuelCluster::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app); \n}\n\nvoid FuelCluster::destroy() {\n    UiElement::destroy();\n}\n\nvoid FuelCluster::update(float dt) {\n    UiElement::update(dt);\n}\n\nvoid FuelCluster::render() {\n    const Bounds bounds = m_bounds.inset(10.0f);\n    const Bounds title = bounds.verticalSplit(1.0f, 0.9f);\n    const Bounds bodyBounds = bounds.verticalSplit(0.0f, 0.9f);\n\n    drawCenteredText(\"FUEL\", title.inset(10.0f), 24.0f);\n\n    Grid grid;\n    grid.h_cells = 1;\n    grid.v_cells = 10;\n\n    std::stringstream ss;\n    ss << std::setprecision(3) << std::fixed;\n    ss << units::convert(getTotalVolumeFuelConsumed(), units::L);\n    ss << \" L\";\n\n    const Bounds totalFuelLiters = grid.get(bodyBounds, 0, 1, 1, 2);\n    drawText(ss.str(), totalFuelLiters, 32.0f, Bounds::lm);\n\n    ss = std::stringstream();\n    ss << std::setprecision(3) << std::fixed;\n    ss << units::convert(getTotalVolumeFuelConsumed(), units::gal);\n    ss << \" gal\";\n\n    const Bounds totalFuelGallons = grid.get(bodyBounds, 0, 3, 1, 1);\n    drawText(ss.str(), totalFuelGallons, 16.0f, Bounds::lm);\n\n    const double fuelConsumed = getTotalVolumeFuelConsumed();\n    const double fuelConsumed_gallons = units::convert(fuelConsumed, units::gal);\n\n    ss = std::stringstream();\n    ss << std::setprecision(2) << std::fixed;\n    ss << \"$\" << 4.761 * fuelConsumed_gallons << \" USD\";\n\n    const Bounds costUSD = grid.get(bodyBounds, 0, 4);\n    drawText(ss.str(), costUSD, 16.0f, Bounds::lm);\n\n    const double travelledDistance = (m_simulator->getVehicle() != nullptr)\n        ? m_simulator->getVehicle()->getTravelledDistance()\n        : 0.0;\n    const double mpg = units::convert(travelledDistance, units::mile) / fuelConsumed_gallons;\n\n    ss = std::stringstream();\n    ss << std::setprecision(2) << std::fixed;\n    ss << mpg << \" MPG\";\n\n    const Bounds mpgBounds = grid.get(bodyBounds, 0, 6);\n    drawText(ss.str(), mpgBounds, 16.0f, Bounds::lm);\n\n    const double lp100km = (travelledDistance != 0)\n        ? units::convert(fuelConsumed, units::L)\n            / (units::convert(travelledDistance, units::km) / 100.0)\n        : 0;\n\n    ss = std::stringstream();\n    ss << std::setprecision(2) << std::fixed;\n    ss << ((lp100km > 100.0) ? 100.0 : lp100km) << \" L/100 KM\";\n\n    const Bounds lp100kmBounds = grid.get(bodyBounds, 0, 7);\n    drawText(ss.str(), lp100kmBounds, 12.0f, Bounds::lm);\n\n    UiElement::render();\n}\n\ndouble FuelCluster::getTotalVolumeFuelConsumed() const {\n    return (m_engine != nullptr)\n        ? m_engine->getTotalVolumeFuelConsumed()\n        : 0.0;\n}\n"
  },
  {
    "path": "src/function.cpp",
    "content": "#include \"../include/function.h\"\n\n#include <algorithm>\n#include <string.h>\n#include <assert.h>\n#include <cmath>\n\nGaussianFilter *Function::DefaultGaussianFilter = nullptr;\n\nFunction::Function() {\n    m_x = m_y = nullptr;\n    m_capacity = 0;\n    m_size = 0;\n    m_filterRadius = 0;\n    m_yMin = m_yMax = 0;\n    m_inputScale = 1.0;\n    m_outputScale = 1.0;\n\n    if (DefaultGaussianFilter == nullptr) {\n        DefaultGaussianFilter = new GaussianFilter;\n        DefaultGaussianFilter->initialize(1.0, 3.0, 1024);\n    }\n\n    m_gaussianFilter = nullptr;\n}\n\nFunction::~Function() {\n    assert(m_x == nullptr);\n    assert(m_y == nullptr);\n}\n\nvoid Function::initialize(int size, double filterRadius, GaussianFilter *filter) {\n    resize(size);\n    m_size = 0;\n    m_filterRadius = filterRadius;\n\n    m_gaussianFilter = (filter != nullptr)\n        ? filter\n        : DefaultGaussianFilter;\n}\n\nvoid Function::resize(int newCapacity) {\n    double *new_x = new double[newCapacity];\n    double *new_y = new double[newCapacity];\n\n    if (m_size > 0) {\n        memcpy(new_x, m_x, sizeof(double) * m_size);\n        memcpy(new_y, m_y, sizeof(double) * m_size);\n    }\n\n    delete[] m_x;\n    delete[] m_y;\n\n    m_x = new_x;\n    m_y = new_y;\n\n    m_capacity = newCapacity;\n}\n\nvoid Function::destroy() {\n    delete[] m_x;\n    delete[] m_y;\n\n    m_x = nullptr;\n    m_y = nullptr;\n\n    m_capacity = 0;\n    m_size = 0;\n}\n\nvoid Function::addSample(double x, double y) {\n    if (m_size + 1 > m_capacity) {\n        resize(m_capacity * 2 + 1);\n    }\n\n    m_yMin = std::fmin(m_yMin, y);\n    m_yMax = std::fmax(m_yMax, y);\n\n    const int closest = closestSample(x);\n    if (closest == -1) {\n        m_size = 1;\n\n        m_x[0] = x;\n        m_y[0] = y;\n        return;\n    }\n\n    const int index = x < m_x[closest]\n        ? closest\n        : closest + 1;\n\n    ++m_size;\n\n    const size_t sizeToCopy = (size_t)m_size - index - 1;\n    if (sizeToCopy > 0) {\n        memmove(m_x + index + 1, m_x + index, sizeof(double) * sizeToCopy);\n        memmove(m_y + index + 1, m_y + index, sizeof(double) * sizeToCopy);\n    }\n\n    m_x[index] = x;\n    m_y[index] = y;\n}\n\ndouble Function::sampleTriangle(double x) const {\n    x *= m_inputScale;\n    const int closest = closestSample(x);\n\n    if (m_size == 0) return 0;\n    else if (x >= m_x[m_size - 1]) return m_y[m_size - 1] * m_outputScale;\n    else if (x <= m_x[0]) return m_y[0] * m_outputScale;\n\n    double sum = 0;\n    double totalWeight = 0;\n    for (int i = closest; i >= 0; --i) {\n        if (m_x[i] > x) continue;\n        if (std::abs(x - m_x[i]) > m_filterRadius) break;\n\n        const double w = triangle(m_x[i] - x);\n        sum += w * m_y[i];\n        totalWeight += w;\n    }\n\n    for (int i = closest; i < m_size; ++i) {\n        if (m_x[i] <= x) continue;\n        if (std::abs(m_x[i] - x) > m_filterRadius) break;\n\n        const double w = triangle(m_x[i] - x);\n        sum += w * m_y[i];\n        totalWeight += w;\n    }\n\n    return (totalWeight != 0)\n        ? sum * m_outputScale / totalWeight\n        : 0;\n}\n\ndouble Function::sampleGaussian(double x) const {\n    x *= m_inputScale;\n    const int closest = closestSample(x);\n    const double filterRadius = m_filterRadius * m_gaussianFilter->getRadius();\n\n    double sum = 0;\n    double totalWeight = 0;\n\n    if (m_size == 0) return 0;\n    else if (x > m_x[m_size - 1]) {\n        const double w = m_gaussianFilter->evaluate(0);\n        sum += w * m_y[m_size - 1];\n        totalWeight += w;\n    }\n    else if (x < m_x[0]) {\n        const double w = m_gaussianFilter->evaluate(0);\n        sum += w * m_y[0];\n        totalWeight += w;\n    }\n\n    for (int i = closest; i >= 0; --i) {\n        if (std::abs(x - m_x[i]) > filterRadius) break;\n\n        const double w = m_gaussianFilter->evaluate((m_x[i] - x) / m_filterRadius);\n        sum += w * m_y[i];\n        totalWeight += w;\n    }\n\n    for (int i = closest + 1; i < m_size; ++i) {\n        if (std::abs(m_x[i] - x) > filterRadius) break;\n\n        const double w = m_gaussianFilter->evaluate((m_x[i] - x) / m_filterRadius);\n        sum += w * m_y[i];\n        totalWeight += w;\n    }\n\n    return (totalWeight != 0)\n        ? sum * m_outputScale / totalWeight\n        : 0;\n}\n\nbool Function::isOrdered() const {\n    for (int i = 0; i < m_size - 1; ++i) {\n        if (m_x[i] > m_x[i + 1]) return false;\n    }\n\n    return true;\n}\n\nvoid Function::getDomain(double *x0, double *x1) {\n    if (m_size == 0) {\n        *x0 = *x1 = 0;\n    }\n    else {\n        *x0 = m_x[0];\n        *x1 = m_x[m_size - 1];\n    }\n}\n\nvoid Function::getRange(double *y0, double *y1) {\n    *y0 = m_yMin;\n    *y1 = m_yMax;\n}\n\ndouble Function::triangle(double x) const {\n    return (m_filterRadius - std::abs(x)) / m_filterRadius;\n}\n\nint Function::closestSample(double x) const {\n    if (std::isnan(x)) {\n        return 0;\n    }\n\n    int l = 0;\n    int r = m_size - 1;\n\n    if (m_size == 0) return -1;\n    else if (x <= m_x[l]) return l;\n    else if (x >= m_x[r]) return r;\n\n    while (l + 1 < r) {\n        const int m = (l + r) / 2;\n        if (x > m_x[m]) {\n            l = m;\n        }\n        else if (x < m_x[m]) {\n            r = m;\n        }\n        else if (x == m_x[m]) {\n            return m;\n        }\n    }\n\n    return (x - m_x[l] < m_x[r] - x)\n        ? l\n        : r;\n}\n"
  },
  {
    "path": "src/gas_system.cpp",
    "content": "#include \"../include/gas_system.h\"\n\n#include \"../include/units.h\"\n#include \"../include/utilities.h\"\n\n#include <cmath>\n#include <cassert>\n\nvoid GasSystem::setGeometry(double width, double height, double dx, double dy) {\n    m_width = width;\n    m_height = height;\n    m_dx = dx;\n    m_dy = dy;\n}\n\nvoid GasSystem::initialize(double P, double V, double T, const Mix &mix, int degreesOfFreedom) {\n    m_degreesOfFreedom = degreesOfFreedom;\n    m_state.n_mol = P * V / (constants::R * T);\n    m_state.V = V;\n    m_state.E_k = T * (0.5 * degreesOfFreedom * m_state.n_mol * constants::R);\n    m_state.mix = mix;\n    m_state.momentum[0] = m_state.momentum[1] = 0;\n\n    const double hcr = heatCapacityRatio();\n    m_chokedFlowLimit = chokedFlowLimit(degreesOfFreedom);\n    m_chokedFlowFactorCached = chokedFlowRate(degreesOfFreedom);\n}\n\nvoid GasSystem::reset(double P, double T, const Mix &mix) {\n    m_state.n_mol = P * volume() / (constants::R * T);\n    m_state.E_k = T * (0.5 * m_degreesOfFreedom * m_state.n_mol * constants::R);\n    m_state.mix = mix;\n    m_state.momentum[0] = m_state.momentum[1] = 0;\n}\n\nvoid GasSystem::setVolume(double V) {\n    return changeVolume(V - m_state.V);\n}\n\nvoid GasSystem::setN(double n) {\n    m_state.E_k = kineticEnergy(n);\n    m_state.n_mol = n;\n}\n\nvoid GasSystem::changeVolume(double dV) {\n    const double V = this->volume();\n    const double L = std::pow(V + dV, 1 / 3.0);\n    const double surfaceArea = (L * L);\n    const double dL = -dV / surfaceArea;\n    const double W = dL * pressure() * surfaceArea;\n\n    m_state.V += dV;\n    m_state.E_k += W;\n}\n\nvoid GasSystem::changePressure(double dP) {\n    m_state.E_k += dP * volume() * m_degreesOfFreedom * 0.5;\n}\n\nvoid GasSystem::changeTemperature(double dT) {\n    m_state.E_k += dT * 0.5 * m_degreesOfFreedom * n() * constants::R;\n}\n\nvoid GasSystem::changeEnergy(double dE) {\n    m_state.E_k += dE;\n}\n\nvoid GasSystem::changeMix(const Mix &mix) {\n    m_state.mix = mix;\n}\n\nvoid GasSystem::injectFuel(double n) {\n    const double n_fuel = this->n_fuel() + n;\n    const double p_fuel = n_fuel / this->n();\n    m_state.mix.p_fuel = p_fuel;\n}\n\nvoid GasSystem::changeTemperature(double dT, double n) {\n    m_state.E_k += dT * 0.5 * m_degreesOfFreedom * n * constants::R;\n}\n\ndouble GasSystem::react(double n, const Mix &mix) {\n    const double l_n_fuel = mix.p_fuel * n;\n    const double l_n_o2 = mix.p_o2 * n;\n\n    const double system_n_fuel = n_fuel();\n    const double system_n_o2 = n_o2();\n    const double system_n_inert = n_inert();\n    const double system_n = this->n();\n\n    // Assuming the following reaction:\n    // 25[O2] + 2[C8H16] -> 16[CO2] + 18[H2O]\n    constexpr double ideal_o2_ratio = 25.0 / 2;\n    constexpr double ideal_fuel_ratio = 2.0 / 25;\n    constexpr double output_input_ratio = (16.0 + 18.0) / (25 + 2);\n\n    const double ideal_fuel_n = ideal_fuel_ratio * l_n_o2;\n    const double ideal_o2_n = ideal_o2_ratio * l_n_fuel;\n    \n    const double a_n_fuel = std::fmin(\n        std::fmin(system_n_fuel, l_n_fuel),\n        ideal_fuel_n);\n    const double a_n_o2 = std::fmin(\n        std::fmin(system_n_o2, l_n_o2),\n        ideal_o2_n);\n\n    const double reactants_n = a_n_fuel + a_n_o2;\n    const double products_n = output_input_ratio * reactants_n;\n    const double dn = products_n - reactants_n;\n\n    m_state.n_mol += dn;\n\n    // Adjust mix\n    const double new_system_n_fuel = system_n_fuel - a_n_fuel;\n    const double new_system_n_o2 = system_n_o2 - a_n_o2;\n    const double new_system_n_inert = system_n_inert + products_n;\n    const double new_system_n = system_n + dn;\n\n    if (new_system_n != 0) {\n        m_state.mix.p_fuel = new_system_n_fuel / new_system_n;\n        m_state.mix.p_inert = new_system_n_inert / new_system_n;\n        m_state.mix.p_o2 = new_system_n_o2 / new_system_n;\n    }\n    else {\n        m_state.mix.p_fuel = m_state.mix.p_inert = m_state.mix.p_o2 = 0;\n    }\n\n    return a_n_fuel;\n}\n\ndouble GasSystem::flowConstant(\n    double targetFlowRate,\n    double P,\n    double pressureDrop,\n    double T,\n    double hcr)\n{\n    const double T_0 = T;\n    const double p_0 = P, p_T = P - pressureDrop; // p_0 = upstream pressure\n\n    const double chokedFlowLimit =\n        std::pow((2.0 / (hcr + 1)), hcr / (hcr - 1));\n    const double p_ratio = p_T / p_0;\n\n    double flowRate = 0;\n    if (p_ratio <= chokedFlowLimit) {\n        // Choked flow\n        flowRate = std::sqrt(hcr);\n        flowRate *= std::pow(2 / (hcr + 1), (hcr + 1) / (2 * (hcr - 1)));\n    }\n    else {\n        flowRate = (2 * hcr) / (hcr - 1);\n        flowRate *= (1 - std::pow(p_ratio, (hcr - 1) / hcr));\n        flowRate = std::sqrt(flowRate);\n        flowRate *= std::pow(p_ratio, 1 / hcr);\n    }\n\n    flowRate *= p_0 / std::sqrt(constants::R * T_0);\n\n    return targetFlowRate / flowRate;\n}\n\ndouble GasSystem::k_28inH2O(double flowRateScfm) {\n    return flowConstant(\n        units::flow(flowRateScfm, units::scfm),\n        units::pressure(1.0, units::atm),\n        units::pressure(28.0, units::inH2O),\n        units::celcius(25),\n        heatCapacityRatio(5)\n    );\n}\n\ndouble GasSystem::k_carb(double flowRateScfm) {\n    return flowConstant(\n        units::flow(flowRateScfm, units::scfm),\n        units::pressure(1.0, units::atm),\n        units::pressure(1.5, units::inHg),\n        units::celcius(25),\n        heatCapacityRatio(5)\n    );\n}\n\ndouble GasSystem::flowRate(\n    double k_flow,\n    double P0,\n    double P1,\n    double T0,\n    double T1,\n    double hcr,\n    double chokedFlowLimit,\n    double chokedFlowRateCached)\n{\n    if (k_flow == 0) return 0;\n\n    double direction;\n    double T_0;\n    double p_0, p_T; // p_0 = upstream pressure\n    if (P0 > P1) {\n        direction = 1.0;\n        T_0 = T0;\n        p_0 = P0;\n        p_T = P1;\n    }\n    else {\n        direction = -1.0;\n        T_0 = T1;\n        p_0 = P1;\n        p_T = P0;\n    }\n\n    const double p_ratio = p_T / p_0;\n    double flowRate = 0;\n    if (p_ratio <= chokedFlowLimit) {\n        // Choked flow\n        flowRate = chokedFlowRateCached;\n        flowRate /= std::sqrt(constants::R * T_0);\n    }\n    else {\n        const double s = std::pow(p_ratio, 1 / hcr);\n\n        flowRate = (2 * hcr) / (hcr - 1);\n        flowRate *= s * (s - p_ratio);\n        flowRate = std::sqrt(std::fmax(flowRate, 0.0) / (constants::R * T_0));\n    }\n\n    flowRate *= direction * p_0;\n\n    return flowRate * k_flow;\n}\n\ndouble GasSystem::loseN(double dn, double E_k_per_mol) {\n    m_state.E_k -= E_k_per_mol * dn;\n    m_state.n_mol -= dn;\n\n    if (m_state.n_mol < 0) {\n        m_state.n_mol = 0;\n    }\n\n    return dn;\n}\n\ndouble GasSystem::gainN(double dn, double E_k_per_mol, const Mix &mix) {\n    const double next_n = m_state.n_mol + dn;\n    const double current_n = m_state.n_mol;\n\n    m_state.E_k += dn * E_k_per_mol;\n    m_state.n_mol = next_n;\n\n    if (next_n != 0) {\n        m_state.mix.p_fuel = (m_state.mix.p_fuel * current_n + dn * mix.p_fuel) / next_n;\n        m_state.mix.p_inert = (m_state.mix.p_inert * current_n + dn * mix.p_inert) / next_n;\n        m_state.mix.p_o2 = (m_state.mix.p_o2 * current_n + dn * mix.p_o2) / next_n;\n    }\n    else {\n        m_state.mix.p_fuel = m_state.mix.p_inert = m_state.mix.p_o2 = 0;\n    }\n\n    return -dn;\n}\n\nvoid GasSystem::dissipateExcessVelocity() {\n    const double v_x = velocity_x();\n    const double v_y = velocity_y();\n    const double v_squared = v_x * v_x + v_y * v_y;\n    const double c = this->c();\n    const double c_squared = c * c;\n\n    if (c_squared >= v_squared || v_squared == 0) {\n        return;\n    }\n\n    const double k_squared = c_squared / v_squared;\n    const double k = std::sqrt(k_squared);\n\n    m_state.momentum[0] *= k;\n    m_state.momentum[1] *= k;\n\n    m_state.E_k += 0.5 * mass() * (v_squared - c_squared);\n\n    if (m_state.E_k < 0) m_state.E_k = 0;\n}\n\nvoid GasSystem::updateVelocity(double dt, double beta) {\n    if (n() == 0) return;\n\n    const double depth = volume() / (m_width * m_height);\n    \n    double d_momentum_x = 0;\n    double d_momentum_y = 0;\n\n    const double p0 = dynamicPressure(m_dx, m_dy);\n    const double p1 = dynamicPressure(-m_dx, -m_dy);\n    const double p2 = dynamicPressure(m_dy, m_dx);\n    const double p3 = dynamicPressure(-m_dy, -m_dx);\n\n    const double p_sa_0 = p0 * (m_height * depth);\n    const double p_sa_1 = p1 * (m_height * depth);\n    const double p_sa_2 = p2 * (m_width * depth);\n    const double p_sa_3 = p3 * (m_width * depth);\n\n    d_momentum_x += p_sa_0 * m_dx;\n    d_momentum_y += p_sa_0 * m_dy;\n\n    d_momentum_x -= p_sa_1 * m_dx;\n    d_momentum_y -= p_sa_1 * m_dy;\n\n    d_momentum_x += p_sa_2 * m_dy;\n    d_momentum_y += p_sa_2 * m_dx;\n\n    d_momentum_x -= p_sa_3 * m_dy;\n    d_momentum_y -= p_sa_3 * m_dx;\n\n    const double m = mass();\n    const double inv_m = 1 / m;\n    const double v0_x = m_state.momentum[0] * inv_m;\n    const double v0_y = m_state.momentum[1] * inv_m;\n\n    m_state.momentum[0] -= d_momentum_x * dt * beta;\n    m_state.momentum[1] -= d_momentum_y * dt * beta;\n\n    const double v1_x = m_state.momentum[0] * inv_m;\n    const double v1_y = m_state.momentum[1] * inv_m;\n\n    m_state.E_k -= 0.5 * m * (v1_x * v1_x - v0_x * v0_x);\n    m_state.E_k -= 0.5 * m * (v1_y * v1_y - v0_y * v0_y);\n\n    if (m_state.E_k < 0) m_state.E_k = 0;\n}\n\nvoid GasSystem::dissipateVelocity(double dt, double timeConstant) {\n    if (n() == 0) return;\n\n    const double invMass = 1.0 / mass();\n    const double velocity_x = m_state.momentum[0] * invMass;\n    const double velocity_y = m_state.momentum[1] * invMass;\n    const double velocity_squared =\n        velocity_x * velocity_x + velocity_y * velocity_y;\n\n    const double s = dt / (dt + timeConstant);\n    m_state.momentum[0] = m_state.momentum[0] * (1 - s);\n    m_state.momentum[1] = m_state.momentum[1] * (1 - s);\n\n    const double newVelocity_x = m_state.momentum[0] * invMass;\n    const double newVelocity_y = m_state.momentum[1] * invMass;\n    const double newVelocity_squared =\n        newVelocity_x * newVelocity_x + newVelocity_y * newVelocity_y;\n\n    const double dE_k = 0.5 * mass() * (velocity_squared - newVelocity_squared);\n    m_state.E_k += dE_k;\n}\n\ndouble GasSystem::flow(const FlowParameters &params) {\n    GasSystem *source = nullptr, *sink = nullptr;\n    double sourcePressure = 0, sinkPressure = 0;\n    double dx, dy;\n    double sourceCrossSection = 0, sinkCrossSection = 0;\n    double direction = 0;\n\n    const double P_0 =\n        params.system_0->pressure()\n        + params.system_0->dynamicPressure(params.direction_x, params.direction_y);\n    const double P_1 =\n        params.system_1->pressure()\n        + params.system_1->dynamicPressure(-params.direction_x, -params.direction_y);\n\n    if (P_0 > P_1) {\n        dx = params.direction_x;\n        dy = params.direction_y;\n        source = params.system_0;\n        sink = params.system_1;\n        sourcePressure = P_0;\n        sinkPressure = P_1;\n        sourceCrossSection = params.crossSectionArea_0;\n        sinkCrossSection = params.crossSectionArea_1;\n        direction = 1.0;\n    }\n    else {\n        dx = -params.direction_x;\n        dy = -params.direction_y;\n        source = params.system_1;\n        sink = params.system_0;\n        sourcePressure = P_1;\n        sinkPressure = P_0;\n        sourceCrossSection = params.crossSectionArea_1;\n        sinkCrossSection = params.crossSectionArea_0;\n        direction = -1.0;\n    }\n\n    double flow = params.dt * flowRate(\n        params.k_flow,\n        sourcePressure,\n        sinkPressure,\n        source->temperature(),\n        sink->temperature(),\n        source->heatCapacityRatio(),\n        source->m_chokedFlowLimit,\n        source->m_chokedFlowFactorCached);\n\n    const double maxFlow = source->pressureEquilibriumMaxFlow(sink);\n    flow = clamp(flow, 0.0, 0.9 * source->n());\n\n    const double fraction = flow / source->n();\n    const double fractionVolume = fraction * source->volume();\n    const double fractionMass = fraction * source->mass();\n    const double remainingMass = (1 - fraction) * source->mass();\n\n    if (flow != 0) {\n        // - Stage 1\n        // Fraction flows from source to sink.\n\n        const double E_k_bulk_src0 = source->bulkKineticEnergy();\n        const double E_k_bulk_sink0 = sink->bulkKineticEnergy();\n\n        const double s0 = source->totalEnergy() + sink->totalEnergy();\n\n        const double E_k_per_mol = source->kineticEnergyPerMol();\n        sink->gainN(flow, E_k_per_mol, source->mix());\n        source->loseN(flow, E_k_per_mol);\n\n        const double s1 = source->totalEnergy() + sink->totalEnergy();\n\n        const double dp_x = source->m_state.momentum[0] * fraction;\n        const double dp_y = source->m_state.momentum[1] * fraction;\n        source->m_state.momentum[0] -= dp_x;\n        source->m_state.momentum[1] -= dp_y;\n\n        sink->m_state.momentum[0] += dp_x;\n        sink->m_state.momentum[1] += dp_y;\n\n        const double E_k_bulk_src1 = source->bulkKineticEnergy();\n        const double E_k_bulk_sink1 = sink->bulkKineticEnergy();\n\n        sink->m_state.E_k -= ((E_k_bulk_src1 + E_k_bulk_sink1) - (E_k_bulk_src0 + E_k_bulk_sink0));\n    }\n    \n    const double sourceMass = source->mass();\n    const double invSourceMass = 1 / sourceMass;\n    const double sinkMass = sink->mass();\n    const double invSinkMass = 1 / sinkMass;\n\n    const double c_source = source->c();\n    const double c_sink = sink->c();\n\n    const double sourceInitialMomentum_x = source->m_state.momentum[0];\n    const double sourceInitialMomentum_y = source->m_state.momentum[1];\n\n    const double sinkInitialMomentum_x = sink->m_state.momentum[0];\n    const double sinkInitialMomentum_y = sink->m_state.momentum[1];\n\n    // Momentum in fraction\n\n    if (sinkCrossSection != 0) {\n        const double sinkFractionVelocity =\n            clamp((fractionVolume / sinkCrossSection) / params.dt, 0.0, c_sink);\n        const double sinkFractionVelocity_squared = sinkFractionVelocity * sinkFractionVelocity;\n        const double sinkFractionVelocity_x = sinkFractionVelocity * dx;\n        const double sinkFractionVelocity_y = sinkFractionVelocity * dy;\n        const double sinkFractionMomentum_x = sinkFractionVelocity_x * fractionMass;\n        const double sinkFractionMomentum_y = sinkFractionVelocity_y * fractionMass;\n\n        sink->m_state.momentum[0] += sinkFractionMomentum_x;\n        sink->m_state.momentum[1] += sinkFractionMomentum_y;\n    }\n\n    if (sourceCrossSection != 0 && sourceMass != 0) {\n        const double sourceFractionVelocity =\n            clamp((fractionVolume / sourceCrossSection) / params.dt, 0.0, c_source);\n        const double sourceFractionVelocity_squared = sourceFractionVelocity * sourceFractionVelocity;\n        const double sourceFractionVelocity_x = sourceFractionVelocity * dx;\n        const double sourceFractionVelocity_y = sourceFractionVelocity * dy;\n        const double sourceFractionMomentum_x = sourceFractionVelocity_x * fractionMass;\n        const double sourceFractionMomentum_y = sourceFractionVelocity_y * fractionMass;\n\n        source->m_state.momentum[0] += sourceFractionMomentum_x;\n        source->m_state.momentum[1] += sourceFractionMomentum_y;\n    }\n\n    if (sourceMass != 0) {\n        // Energy conservation\n        const double sourceVelocity0_x = sourceInitialMomentum_x * invSourceMass;\n        const double sourceVelocity0_y = sourceInitialMomentum_y * invSourceMass;\n\n        const double sourceVelocity1_x = source->m_state.momentum[0] * invSourceMass;\n        const double sourceVelocity1_y = source->m_state.momentum[1] * invSourceMass;\n\n        source->m_state.E_k -=\n            0.5 * sourceMass\n            * (sourceVelocity1_x * sourceVelocity1_x - sourceVelocity0_x * sourceVelocity0_x);\n\n        source->m_state.E_k -=\n            0.5 * sourceMass\n            * (sourceVelocity1_y * sourceVelocity1_y - sourceVelocity0_y * sourceVelocity0_y);\n    }\n\n    if (sinkMass > 0) {\n        const double sinkVelocity0_x = sinkInitialMomentum_x * invSinkMass;\n        const double sinkVelocity0_y = sinkInitialMomentum_y * invSinkMass;\n\n        const double sinkVelocity1_x = sink->m_state.momentum[0] * invSinkMass;\n        const double sinkVelocity1_y = sink->m_state.momentum[1] * invSinkMass;\n\n        sink->m_state.E_k -=\n            0.5 * sinkMass\n            * (sinkVelocity1_x * sinkVelocity1_x - sinkVelocity0_x * sinkVelocity0_x);\n\n        sink->m_state.E_k -=\n            0.5 * sinkMass\n            * (sinkVelocity1_y * sinkVelocity1_y - sinkVelocity0_y * sinkVelocity0_y);\n    }\n\n    if (sink->m_state.E_k < 0) {\n        sink->m_state.E_k = 0;\n    }\n\n    if (source->m_state.E_k < 0) {\n        source->m_state.E_k = 0;\n    }\n\n    return flow * direction;\n}\n\ndouble GasSystem::flow(double k_flow, double dt, double P_env, double T_env, const Mix &mix) {\n    const double maxFlow = pressureEquilibriumMaxFlow(P_env, T_env);\n    double flow = dt * flowRate(\n        k_flow,\n        pressure(),\n        P_env,\n        temperature(),\n        T_env,\n        heatCapacityRatio(),\n        m_chokedFlowLimit,\n        m_chokedFlowFactorCached);\n\n    if (std::abs(flow) > std::abs(maxFlow)) {\n        flow = maxFlow;\n    }\n\n    if (flow < 0) {\n        const double bulk_E_k_0 = bulkKineticEnergy();\n        gainN(-flow, kineticEnergyPerMol(T_env, m_degreesOfFreedom), mix);\n        const double bulk_E_k_1 = bulkKineticEnergy();\n\n        m_state.E_k += (bulk_E_k_1 - bulk_E_k_0);\n    }\n    else {\n        const double starting_n = n();\n        loseN(flow, kineticEnergyPerMol());\n\n        m_state.momentum[0] -= (flow / starting_n) * m_state.momentum[0];\n        m_state.momentum[1] -= (flow / starting_n) * m_state.momentum[1];\n    }\n\n    return flow;\n}\n\ndouble GasSystem::pressureEquilibriumMaxFlow(const GasSystem *b) const {\n    // pressure_a = (kineticEnergy() + n * b->kineticEnergyPerMol()) / (0.5 * degreesOfFreedom * volume())\n    // pressure_b = (b->kineticEnergy() - n *  / (0.5 * b->degreesOfFreedom * b->volume())\n    // pressure_a = pressure_b\n\n    // E_a = kineticEnergy()\n    // E_b = b->kineticEnergy()\n    // D_a = E_a / n()\n    // D_b = E_b / b->n()\n    // Q_a = 1 / (0.5 * degreesOfFreedom * volume())\n    // Q_b = 1 / (0.5 * b->degreesOfFreedom * b->volume())\n    // pressure_a = Q_a * (E_a + dn * D_b)\n    // pressure_b = Q_b * (E_b - dn * D_b)\n\n    if (pressure() > b->pressure()) {\n        const double maxFlow =\n                (b->volume() * kineticEnergy() - volume() * b->kineticEnergy()) /\n                (b->volume() * kineticEnergyPerMol() + volume() * kineticEnergyPerMol());\n        return std::fmax(0.0, std::fmin(maxFlow, n()));\n    }\n    else {\n        const double maxFlow =\n                (b->volume() * kineticEnergy() - volume() * b->kineticEnergy()) /\n                (b->volume() * b->kineticEnergyPerMol() + volume() * b->kineticEnergyPerMol());\n        return std::fmin(0.0, std::fmax(maxFlow, -b->n()));\n    }\n}\n\ndouble GasSystem::pressureEquilibriumMaxFlow(double P_env, double T_env) const {\n    if (pressure() > P_env) {\n        return -(P_env * (0.5 * m_degreesOfFreedom * volume()) - kineticEnergy()) / kineticEnergyPerMol();\n    }\n    else {\n        const double E_k_per_mol_env = 0.5 * T_env * constants::R * m_degreesOfFreedom;\n        return -(P_env * (0.5 * m_degreesOfFreedom * volume()) - kineticEnergy()) / E_k_per_mol_env;\n    }\n}\n"
  },
  {
    "path": "src/gauge.cpp",
    "content": "#include \"../include/gauge.h\"\n\n#include \"../include/engine_sim_application.h\"\n#include \"../include/constants.h\"\n\nGauge::Gauge() {\n    m_thetaMin = (float)constants::pi;\n    m_thetaMax = 0.0f;\n\n    m_min = m_max = 0;\n    m_maxMinorTick = INT_MAX;\n    m_value = 0;\n    m_minorStep = 1;\n    m_majorStep = 10;\n\n    m_minorTickWidth = 1.0f;\n    m_majorTickWidth = 2.0f;\n\n    m_minorTickLength = 5.0f;\n    m_majorTickLength = 10.0f;\n\n    m_outerRadius = 0.0f;\n    m_renderText = false;\n\n    m_needleInnerRadius = 0.0f;\n    m_needleOuterRadius = 0.0f;\n    m_needleWidth = 1.0f;\n\n    m_needlePosition = 0.0f;\n    m_needleVelocity = 0.0f;\n    m_needleMaxVelocity = 2.0f;\n    m_needleKs = 1000.0f;\n    m_needleKd = 25.0f;\n\n    m_gamma = 1.0f;\n}\n\nGauge::~Gauge() {\n    /* void */\n}\n\nvoid Gauge::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n}\n\nvoid Gauge::destroy() {\n    /* void */\n}\n\nvoid Gauge::update(float dt) {\n    const float value = std::fmaxf((float)m_min, std::fmin((float)m_max, (float)m_value));\n    const float needle_s = std::powf((value - m_min) / std::abs(m_max - m_min), m_gamma);\n    const float F =\n        m_needleKs * (needle_s - m_needlePosition)\n        - m_needleKd * m_needleVelocity;\n\n    m_needleVelocity = std::fminf(\n            m_needleMaxVelocity,\n            std::fmaxf(m_needleVelocity + F * dt, -m_needleMaxVelocity));\n    m_needlePosition += m_needleVelocity * dt;\n    m_needlePosition = std::fmax(0.0f, std::fmin(1.0f, m_needlePosition));\n}\n\nvoid Gauge::render() {\n    GeometryGenerator *generator = m_app->getGeometryGenerator();\n\n    const Point origin = getRenderPoint(m_bounds.getPosition(Bounds::center) + m_center);\n    const float outerRadius = pixelsToUnits(m_outerRadius);\n    const float minorTickWidth = pixelsToUnits(m_minorTickWidth);\n    const float majorTickWidth = pixelsToUnits(m_majorTickWidth);\n    const float minorTickLength = pixelsToUnits(m_minorTickLength);\n    const float majorTickLength = pixelsToUnits(m_majorTickLength);\n\n    GeometryGenerator::GeometryIndices ticks, needle;\n\n    GeometryGenerator::Line2dParameters lineParams;\n    generator->startShape();\n    for (int i = 0; i <= std::abs(m_max - m_min); i += m_minorStep) {\n        const float s = std::powf((float)i / std::abs(m_max - m_min), m_gamma);\n        const float theta = s * m_thetaMax + (1 - s) * m_thetaMin;\n\n        const float tickLength = (i % m_majorStep) == 0\n            ? majorTickLength\n            : minorTickLength;\n\n        const float tickWidth = (i % m_majorStep) == 0\n            ? majorTickWidth\n            : minorTickWidth;\n\n        const Point dir(std::cos(theta), std::sin(theta));\n        const Point inner = dir * (outerRadius - tickLength) + origin;\n        const Point outer = dir * outerRadius + origin;\n        const Point text = dir * (outerRadius - majorTickLength * 2) + origin;\n\n        lineParams.lineWidth = tickWidth;\n        lineParams.x0 = inner.x;\n        lineParams.x1 = outer.x;\n        lineParams.y0 = inner.y;\n        lineParams.y1 = outer.y;\n\n        if ((i % m_majorStep) == 0 || (i + m_minorStep) <= m_maxMinorTick) {\n            generator->generateLine2d(lineParams);\n        }\n\n        if ((i % m_majorStep) == 0 && m_renderText) {\n            drawCenteredText(\n                    \"n\",\n                    Bounds(0.0f, 0.0f, unitsToPixels(text - origin) + m_bounds.getPosition(Bounds::center) + m_center, Bounds::center),\n                    12);\n        }\n    }\n\n    generator->endShape(&ticks);\n\n    generator->startShape();\n\n    const float needleAngle = m_needlePosition * m_thetaMax + (1 - m_needlePosition) * m_thetaMin;\n    const Point needleDir(std::cos(needleAngle), std::sin(needleAngle));\n    const Point needleOuter = needleDir * pixelsToUnits(m_needleOuterRadius) + origin;\n    const Point needleInner = needleDir * pixelsToUnits(m_needleInnerRadius) + origin;\n\n    lineParams.lineWidth = pixelsToUnits(m_needleWidth);\n    lineParams.x0 = needleInner.x;\n    lineParams.x1 = needleOuter.x;\n    lineParams.y0 = needleInner.y;\n    lineParams.y1 = needleOuter.y;\n    generator->generateLine2d(lineParams);\n    generator->endShape(&needle);\n\n    resetShader();\n\n    GeometryGenerator::Ring2dParameters ringParams;\n    ringParams.arrowOnEnd = false;\n    ringParams.drawArrow = false;\n    ringParams.center_x = origin.x;\n    ringParams.center_y = origin.y;\n    ringParams.maxEdgeLength = pixelsToUnits(5);\n    for (const Band &band : m_bands) {\n        ringParams.outerRadius = outerRadius + pixelsToUnits(band.radial_offset);\n        ringParams.innerRadius = outerRadius + pixelsToUnits(band.radial_offset - band.width);\n\n        const float s0 = std::powf((float)(band.start - m_min) / std::abs(m_max - m_min), m_gamma);\n        const float angle0 = s0 * m_thetaMax + (1 - s0) * m_thetaMin;\n\n        const float s1 = std::powf((float)(band.end - m_min) / std::abs(m_max - m_min), m_gamma);\n        const float angle1 = s1 * m_thetaMax + (1 - s1) * m_thetaMin;\n\n        ringParams.startAngle = std::fminf(angle0, angle1) + band.shorten_end;\n        ringParams.endAngle = std::fmaxf(angle0, angle1) - band.shorten_start;\n\n        GeometryGenerator::GeometryIndices bandIndices;\n        generator->startShape();\n        generator->generateRing2d(ringParams);\n        generator->endShape(&bandIndices);\n\n        m_app->getShaders()->SetBaseColor(band.color);\n        m_app->drawGenerated(bandIndices, 0x11, m_app->getShaders()->GetUiFlags());\n    }\n\n    m_app->getShaders()->SetBaseColor(m_app->getForegroundColor());\n    m_app->drawGenerated(ticks, 0x11, m_app->getShaders()->GetUiFlags());\n\n    m_app->getShaders()->SetBaseColor(m_app->getHightlight1Color());\n    m_app->drawGenerated(needle, 0x11, m_app->getShaders()->GetUiFlags());\n}\n"
  },
  {
    "path": "src/gaussian_filter.cpp",
    "content": "#include \"../include/gaussian_filter.h\"\n\n#include <cmath>\n\nGaussianFilter::GaussianFilter() {\n    m_cache = nullptr;\n\n    m_cacheSteps = 0;\n    m_radius = 0.0;\n    m_alpha = 0.0;\n\n    m_exp_s = 0.0;\n    m_inv_r = 0.0;\n}\n\nGaussianFilter::~GaussianFilter() {\n    if (m_cache != nullptr) delete[] m_cache;\n}\n\nvoid GaussianFilter::initialize(double alpha, double radius, int cacheSteps) {\n    m_cacheSteps = cacheSteps;\n\n    m_alpha = alpha;\n    m_radius = radius;\n\n    m_exp_s = std::exp(-m_alpha * m_radius * m_radius);\n    m_inv_r = 1 / m_radius;\n\n    generateCache();\n}\n\ndouble GaussianFilter::evaluate(double s) const {\n    const int actualSteps = m_cacheSteps - 32;\n    const double s_sample = actualSteps * std::abs(s) * m_inv_r;\n    const double s0 = std::floor(s_sample);\n    const double s1 = std::ceil(s_sample);\n    const double d = s_sample - s0;\n\n    return\n        (1 - d) * m_cache[(int)s0]\n        + d * m_cache[(int)s1];\n}\n\ndouble GaussianFilter::calculate(double s) const {\n    return std::max(\n            0.0,\n            std::exp(-m_alpha * s * s) - m_exp_s);\n}\n\nvoid GaussianFilter::generateCache() {\n    const int actualSteps = m_cacheSteps - 32;\n    const double step = 1.0 / actualSteps;\n\n    m_cache = new double[m_cacheSteps];\n    for (int i = 0; i <= actualSteps; ++i) {\n        const double s = i * step * m_radius;\n        m_cache[i] = calculate(s);\n    }\n\n    for (int i = actualSteps + 1; i < m_cacheSteps; ++i) {\n        m_cache[i] = 0.0;\n    }\n}\n"
  },
  {
    "path": "src/geometry_generator.cpp",
    "content": "#include \"../include/geometry_generator.h\"\n\nGeometryGenerator::GeometryGenerator() {\n    m_vertexData = nullptr;\n    m_indexData = nullptr;\n\n    m_state.vertexPointer = 0;\n    m_state.indexPointer = 0;\n\n    m_indexBufferSize = 0;\n    m_vertexBufferSize = 0;\n\n    m_state.subshapeVertexPointer = 0;\n}\n\nGeometryGenerator::~GeometryGenerator() {\n    /* void */\n}\n\nvoid GeometryGenerator::initialize(int vertexBufferSize, int indexBufferSize) {\n    m_vertexData = new dbasic::Vertex[vertexBufferSize];\n    m_indexData = new unsigned short[indexBufferSize];\n\n    m_vertexBufferSize = vertexBufferSize;\n    m_indexBufferSize = indexBufferSize;\n}\n\nvoid GeometryGenerator::destroy() {\n    delete[] m_vertexData;\n    delete[] m_indexData;\n}\n\nvoid GeometryGenerator::reset() {\n    m_state.vertexPointer = 0;\n    m_state.indexPointer = 0;\n    m_state.subshapeVertexPointer = 0;\n}\n\nbool GeometryGenerator::generateFilledCircle(\n    const ysVector &normal,\n    const ysVector &center,\n    float radius,\n    float maxEdgeLength)\n{\n    // edge_length = (sin(theta) * radius) * 2\n    // theta = arcsin(edge_length / (2 * radius))\n\n    const float angle = std::asinf(maxEdgeLength / (2 * radius));\n    const float steps = ysMath::Constants::TWO_PI / angle;\n\n    int wholeSteps = (int)std::ceilf(steps);\n    wholeSteps = (wholeSteps < 3)\n        ? 3\n        : wholeSteps;\n\n    return generateFilledFanPolygon(\n        normal,\n        findOrthogonal(normal),\n        center,\n        radius,\n        0.0f,\n        wholeSteps);\n}\n\nbool GeometryGenerator::generateFilledFanPolygon(\n    const ysVector &normal,\n    const ysVector &up,\n    const ysVector &center,\n    float radius,\n    float rotation,\n    int segmentCount)\n{\n    startSubshape();\n\n    const int vertexCount = 1 + segmentCount;\n    const int faceCount = segmentCount;\n    const int indexCount = faceCount * 3;\n\n    if (vertexCount + m_state.vertexPointer > m_vertexBufferSize ||\n        indexCount + m_state.indexPointer > m_indexBufferSize)\n    {\n        return false;\n    }\n\n    // Generate center vertex\n    dbasic::Vertex *centerVertex = writeVertex();\n    centerVertex->Normal = ysMath::GetVector4(normal);\n    centerVertex->Pos = ysMath::GetVector4(center);\n    centerVertex->TexCoord = ysVector2(0.5f, 0.5f);\n\n    const float angleStep = ysMath::Constants::TWO_PI / segmentCount;\n\n    const ysVector right = ysMath::Cross(up, normal);\n    ysMatrix T = ysMath::LoadMatrix(\n        right,\n        up,\n        normal,\n        ysMath::ExtendVector(center)\n    );\n    T = ysMath::Transpose(T);\n\n    for (int i = 0; i < segmentCount; ++i) {\n        const float angle0 = angleStep * i + rotation;\n        const float x0 = std::cosf(angle0);\n        const float y0 = std::sinf(angle0);\n\n        const ysVector pos = ysMath::LoadVector(x0 * radius, y0 * radius, 0.0f, 1.0f);\n\n        dbasic::Vertex *newVertex = writeVertex();\n        newVertex->Normal = normal;\n        newVertex->Pos = ysMath::MatMult(T, pos);\n        newVertex->TexCoord = ysVector2(0.5f * x0 + 0.5f, 0.5f * y0 + 0.5f);\n    }\n\n    for (int i = 0; i < segmentCount; ++i) {\n        writeFace(0, i + 1, 1 + ((i + 1) % segmentCount));\n    }\n\n    return true;\n}\n\nbool GeometryGenerator::generateLineRing(\n    const LineRingParameters &params)\n{\n    // edge_length = (sin(theta) * radius) * 2\n    // theta = arcsin(edge_length / (2 * radius))\n\n    startSubshape();\n\n    const float actualStartAngle = params.startAngle - params.taperTail;\n    const float actualEndAngle = params.endAngle + params.taperTail;\n\n    const float maxOuterRadius = params.radius + (params.patternHeight / 2);\n\n    const float angle = std::asinf(params.maxEdgeLength / (2 * maxOuterRadius));\n    const float steps = (actualEndAngle - actualStartAngle) / angle;\n\n    int segmentCount = (int)std::ceilf(steps);\n    segmentCount = (segmentCount < 3)\n        ? 3\n        : segmentCount;\n\n    const int vertexCount = (segmentCount + 1) * 2;\n    const int faceCount = segmentCount * 2;\n    const int indexCount = faceCount * 3;\n\n    const ysVector up = findOrthogonal(params.normal);\n\n    if (vertexCount + m_state.vertexPointer > m_vertexBufferSize ||\n        indexCount + m_state.indexPointer > m_indexBufferSize)\n    {\n        return false;\n    }\n\n    // Generate center vertex\n    const float angleStep = (actualEndAngle - actualStartAngle) / segmentCount;\n\n    const ysVector right = ysMath::Cross(up, params.normal);\n    ysMatrix T = ysMath::LoadMatrix(\n        right,\n        up,\n        params.normal,\n        ysMath::ExtendVector(params.center)\n    );\n    T = ysMath::Transpose(T);\n\n    for (int i = 0; i <= segmentCount; ++i) {\n        float angle0 = angleStep * i + actualStartAngle;\n        const float x0 = std::cosf(angle0);\n        const float y0 = std::sinf(angle0);\n\n        if (angle0 >= actualEndAngle) angle0 = actualEndAngle;\n        else if (angle0 <= actualStartAngle) angle0 = actualStartAngle;\n\n        float taper = 1.0f;\n        if (params.taperTail != 0) {\n            if (angle0 >= actualStartAngle && angle0 < params.startAngle) {\n                taper = (angle0 - actualStartAngle) / params.taperTail;\n            }\n            else if (angle0 > params.endAngle && angle0 <= actualEndAngle) {\n                taper = 1.0f - (angle0 - params.endAngle) / params.taperTail;\n            }\n        }\n\n        const float innerRadius = params.radius - (params.patternHeight / 2) * taper;\n        const float outerRadius = params.radius + (params.patternHeight / 2) * taper;\n\n        const ysVector outerPos =\n            ysMath::LoadVector(x0 * outerRadius, y0 * outerRadius, 0.0f, 1.0f);\n        const ysVector innerPos =\n            ysMath::LoadVector(x0 * innerRadius, y0 * innerRadius, 0.0f, 1.0f);\n\n        const float s =\n            params.textureOffset +\n            angle0 * params.radius / (params.patternHeight * params.textureWidthHeightRatio);\n\n        dbasic::Vertex *outerVertex = writeVertex();\n        outerVertex->Normal = params.normal;\n        outerVertex->Pos = ysMath::MatMult(T, outerPos);\n        outerVertex->TexCoord = ysVector2(s, 1.0f);\n\n        dbasic::Vertex *innerVertex = writeVertex();\n        innerVertex->Normal = params.normal;\n        innerVertex->Pos = ysMath::MatMult(T, innerPos);\n        innerVertex->TexCoord = ysVector2(s, 0.0f);\n    }\n\n#define OUTER(i) (((i)) * 2)\n#define INNER(i) (((i)) * 2 + 1)\n\n    for (int i = 0; i < segmentCount; ++i) {\n        writeFace(INNER(i), OUTER(i + 1), INNER(i + 1));\n        writeFace(INNER(i), OUTER(i), OUTER(i + 1));\n    }\n\n    return true;\n}\n\nbool GeometryGenerator::generateLineRingBalanced(\n    const LineRingParameters &params)\n{\n    const float midpoint = (params.startAngle + params.endAngle) / 2.0f;\n    const float offset =\n        params.textureOffset -\n        (midpoint * params.radius) / (params.patternHeight * params.textureWidthHeightRatio);\n\n    LineRingParameters augmentedParams = params;\n    augmentedParams.textureOffset = offset;\n\n    generateLineRing(augmentedParams);\n\n    return true;\n}\n\nbool GeometryGenerator::generateLine(\n    const LineParameters &params)\n{\n    startSubshape();\n\n    const ysVector tangent = ysMath::Normalize(ysMath::Sub(params.end, params.start));\n    const ysVector right = ysMath::Cross(params.normal, tangent);\n\n    const float length =\n        ysMath::GetScalar(ysMath::Magnitude(ysMath::Sub(params.end, params.start)));\n\n    ysMatrix T0 = ysMath::LoadMatrix(\n        right,\n        tangent,\n        params.normal,\n        ysMath::ExtendVector(params.start)\n    );\n    T0 = ysMath::Transpose(T0);\n\n    ysMatrix T1 = ysMath::LoadMatrix(\n        right,\n        tangent,\n        params.normal,\n        ysMath::ExtendVector(params.end)\n    );\n    T1 = ysMath::Transpose(T1);\n\n    const ysVector v0 = { params.patternHeight / 2.0f, 0.0f, 0.0f, 1.0f };\n    const ysVector v1 = { -params.patternHeight / 2.0f, 0.0f, 0.0f, 1.0f };\n\n    const float ds = 1 / (params.patternHeight * params.textureWidthHeightRatio);\n\n    dbasic::Vertex *vertex = writeVertex();\n    vertex->Normal = params.normal;\n    vertex->Pos = ysMath::MatMult(T0, v0);\n    vertex->TexCoord = ysVector2(0.0f, 1.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = params.normal;\n    vertex->Pos = ysMath::MatMult(T0, v1);\n    vertex->TexCoord = ysVector2(0.0f, 0.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = params.normal;\n    vertex->Pos = ysMath::MatMult(T1, v0);\n    vertex->TexCoord = ysVector2(ds * length + params.textureOffset, 1.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = params.normal;\n    vertex->Pos = ysMath::MatMult(T1, v1);\n    vertex->TexCoord = ysVector2(ds * length + params.textureOffset, 0.0f);\n\n    writeFace(0, 1, 2);\n    writeFace(1, 3, 2);\n\n    if (params.taperTail > 0) {\n        const int steps = 10;\n        const float step = params.taperTail / steps;\n        for (int i = 0; i < steps; ++i) {\n            const float scale = (steps - i - 1) / (float)steps;\n\n            vertex = writeVertex();\n            vertex->Normal = params.normal;\n            vertex->Pos = ysMath::MatMult(\n                    T0,\n                    ysMath::LoadVector(\n                        scale * params.patternHeight / 2.0f,\n                        -step * (i + 1),\n                        0.0f,\n                        1.0f));\n            vertex->TexCoord = ysVector2(-ds * step * (i + 1) + params.textureOffset, 1.0f);\n\n            vertex = writeVertex();\n            vertex->Normal = params.normal;\n            vertex->Pos = ysMath::MatMult(\n                    T0,\n                    ysMath::LoadVector(\n                        -scale * params.patternHeight / 2.0f,\n                        -step * (i + 1),\n                        0.0f,\n                        1.0f));\n            vertex->TexCoord = ysVector2(-ds * step * (i + 1) + params.textureOffset, 0.0f);\n\n            vertex = writeVertex();\n            vertex->Normal = params.normal;\n            vertex->Pos = ysMath::MatMult(\n                    T1,\n                    ysMath::LoadVector(\n                        scale * params.patternHeight / 2.0f,\n                        step * (i + 1),\n                        0.0f,\n                        1.0f));\n            vertex->TexCoord = ysVector2(\n                    ds * (length + step * (i + 1)) + params.textureOffset,\n                    1.0f);\n\n            vertex = writeVertex();\n            vertex->Normal = params.normal;\n            vertex->Pos = ysMath::MatMult(\n                    T1, ysMath::LoadVector(\n                        -scale * params.patternHeight / 2.0f,\n                        step * (i + 1),\n                        0.0f,\n                        1.0f));\n            vertex->TexCoord = ysVector2(\n                    ds * (length + step * (i + 1)) + params.textureOffset, 0.0f);\n\n            writeFace((i + 1) * 4, (i + 1) * 4 + 1, i * 4);\n            writeFace((i + 1) * 4 + 1, i * 4 + 1, i * 4);\n\n            writeFace(i * 4 + 2, i * 4 + 3, (i + 1) * 4 + 2);\n            writeFace(i * 4 + 3, (i + 1) * 4 + 3, (i + 1) * 4 + 2);\n        }\n    }\n\n    return true;\n}\n\nbool GeometryGenerator::generateLine2d(\n        const Line2dParameters &params)\n{\n    startSubshape();\n\n    const float dx = params.x1 - params.x0;\n    const float dy = params.y1 - params.y0;\n    const float length = std::sqrt(dx * dx + dy * dy);\n\n    const float dir_x = dx / length;\n    const float dir_y = dy / length;\n\n    const float perp_x = -dir_y;\n    const float perp_y = dir_x;\n\n    const int vertexCount = 4;\n    const int indexCount = 6;\n\n    if (!checkCapacity(vertexCount, indexCount)) {\n        return false;\n    }\n\n    dbasic::Vertex *vertex;\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n            params.x0 + perp_x * params.lineWidth / 2,\n            params.y0 + perp_y * params.lineWidth / 2);\n    vertex->TexCoord = ysVector2(0.0f, 0.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n        params.x0 - perp_x * params.lineWidth / 2,\n        params.y0 - perp_y * params.lineWidth / 2);\n    vertex->TexCoord = ysVector2(0.0f, 0.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n        params.x1 + perp_x * params.lineWidth / 2,\n        params.y1 + perp_y * params.lineWidth / 2);\n    vertex->TexCoord = ysVector2(0.0f, 0.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n            params.x1 - perp_x * params.lineWidth / 2,\n            params.y1 - perp_y * params.lineWidth / 2);\n    vertex->TexCoord = ysVector2(0.0f, 0.0f);\n\n    writeFace(0, 1, 2);\n    writeFace(1, 3, 2);\n\n    return true;\n}\n\nbool GeometryGenerator::generateFrame(const FrameParameters &params) {\n    State prevState = m_state;\n\n    Line2dParameters lineParams;\n    lineParams.lineWidth = params.lineWidth;\n\n    lineParams.x0 = params.x - params.frameWidth / 2 - params.lineWidth / 2;\n    lineParams.x1 = params.x + params.frameWidth / 2 + params.lineWidth / 2;\n\n    lineParams.y0 = lineParams.y1 = params.y + params.frameHeight / 2;\n    if (!generateLine2d(lineParams)) goto fail;\n\n    lineParams.y0 = lineParams.y1 = params.y - params.frameHeight / 2;\n    if (!generateLine2d(lineParams)) goto fail;\n\n    lineParams.y0 = params.y - params.frameHeight / 2 - params.lineWidth / 2;\n    lineParams.y1 = params.y + params.frameHeight / 2 + params.lineWidth / 2;\n\n    lineParams.x0 = lineParams.x1 = params.x + params.frameWidth / 2;\n    if (!generateLine2d(lineParams)) goto fail;\n\n    lineParams.x0 = lineParams.x1 = params.x - params.frameWidth / 2;\n    if (!generateLine2d(lineParams)) goto fail;\n\n    return true;\n\nfail:\n    m_state = prevState;\n    return false;\n}\n\nbool GeometryGenerator::generateGrid(const GridParameters &params) {\n    const State prevState = m_state;\n\n    Line2dParameters lineParams;\n    lineParams.lineWidth = params.lineWidth;\n\n    lineParams.x0 = params.x - params.width / 2;\n    lineParams.x1 = params.x + params.width / 2;\n\n    for (float offset = 0.0f; offset <= params.height / 2.0f; offset += params.div_y) {\n        lineParams.y0 = lineParams.y1 = params.y + offset;\n        if (!generateLine2d(lineParams)) goto fail;\n\n        lineParams.y0 = lineParams.y1 = params.y - offset;\n        if (!generateLine2d(lineParams)) goto fail;\n    }\n\n    lineParams.y0 = params.y - params.height / 2;\n    lineParams.y1 = params.y + params.height / 2;\n\n    for (float offset = 0.0f; offset <= params.width / 2.0f; offset += params.div_x) {\n        lineParams.x0 = lineParams.x1 = params.x + offset;\n        if (!generateLine2d(lineParams)) goto fail;\n\n        lineParams.x0 = lineParams.x1 = params.x - offset;\n        if (!generateLine2d(lineParams)) goto fail;\n    }\n\n    return true;\n\nfail:\n    m_state = prevState;\n    return false;\n}\n\nbool GeometryGenerator::generateRing2d(const Ring2dParameters &params) {\n    // edge_length = (sin(theta) * radius) * 2\n    // theta = arcsin(edge_length / (2 * radius))\n\n    startSubshape();\n\n    const float angle = std::asinf(params.maxEdgeLength / (2 * params.outerRadius));\n    const float steps = (params.endAngle - params.startAngle) / angle;\n\n    int segmentCount = (int)std::ceilf(steps);\n    segmentCount = (segmentCount < 3)\n        ? 3\n        : segmentCount;\n\n    const int vertexCount = (segmentCount + 1) * 2;\n    const int faceCount = segmentCount * 2;\n    const int indexCount = faceCount * 3;\n\n    if (!checkCapacity(vertexCount, indexCount)) {\n        return false;\n    }\n\n    const float angleStep = (params.endAngle - params.startAngle) / segmentCount;\n\n    float arrowStart = 0.0f;\n    float arrowEnd = 0.0f;\n    if (params.drawArrow) {\n        if (params.arrowOnEnd) {\n            arrowStart = params.endAngle - params.arrowLength;\n            arrowEnd = params.endAngle;\n        }\n        else {\n            arrowStart = params.startAngle + params.arrowLength;\n            arrowEnd = params.startAngle;\n        }\n    }\n\n    const float midRadius = (params.outerRadius + params.innerRadius) / 2;\n    const float fullWidth = params.outerRadius - params.innerRadius;\n    for (int i = 0; i <= segmentCount; ++i) {\n        float angle0 = angleStep * i + params.startAngle;\n        const float x0 = std::cosf(angle0);\n        const float y0 = std::sinf(angle0);\n\n        const float s = (params.drawArrow)\n            ? (angle0 - arrowStart) / (arrowEnd - arrowStart)\n            : 0.0f;\n\n        const float width = fullWidth * (1 - std::fminf(1.0, std::fmaxf(s, 0.0)));\n        const float innerRadius = midRadius - width / 2;\n        const float outerRadius = midRadius + width / 2;\n\n        if (angle0 >= params.endAngle) angle0 = params.endAngle;\n        else if (angle0 <= params.startAngle) angle0 = params.startAngle;\n\n        const ysVector outerPos =\n            ysMath::LoadVector(\n                    x0 * outerRadius + params.center_x,\n                    y0 * outerRadius + params.center_y, 0.0f, 1.0f);\n        const ysVector innerPos =\n            ysMath::LoadVector(\n                    x0 * innerRadius + params.center_x,\n                    y0 * innerRadius + params.center_y, 0.0f, 1.0f);\n\n        dbasic::Vertex *outerVertex = writeVertex();\n        outerVertex->Normal = ysMath::Constants::ZAxis;\n        outerVertex->Pos = outerPos;\n        outerVertex->TexCoord = ysVector2(0.0f, 0.0f);\n\n        dbasic::Vertex *innerVertex = writeVertex();\n        innerVertex->Normal = ysMath::Constants::ZAxis;\n        innerVertex->Pos = innerPos;\n        innerVertex->TexCoord = ysVector2(0.0f, 0.0f);\n    }\n\n#define OUTER(i) (((i)) * 2)\n#define INNER(i) (((i)) * 2 + 1)\n\n    for (int i = 0; i < segmentCount; ++i) {\n        writeFace(INNER(i), OUTER(i + 1), INNER(i + 1));\n        writeFace(INNER(i), OUTER(i), OUTER(i + 1));\n    }\n\n    return true;\n}\n\nbool GeometryGenerator::generateCircle2d(const Circle2dParameters &params) {\n    // edge_length = (sin(theta) * radius) * 2\n    // theta = arcsin(edge_length / (2 * radius))\n    // theta2 = PI - theta\n\n    startSubshape();\n\n    float angle = std::asinf(params.maxEdgeLength / (2 * params.radius)) * 2;\n    angle = std::fminf(angle, ysMath::Constants::PI - params.smallestAngle);\n\n    const float steps = ysMath::Constants::TWO_PI / angle;\n\n    int segmentCount = (int)std::ceilf(steps);\n    segmentCount = (segmentCount < 3)\n        ? 3\n        : segmentCount;\n\n    const int vertexCount = 1 + segmentCount;\n    const int faceCount = segmentCount;\n    const int indexCount = faceCount * 3;\n\n    if (!checkCapacity(vertexCount, indexCount)) {\n        return false;\n    }\n\n    // Generate center vertex\n    dbasic::Vertex *centerVertex = writeVertex();\n    centerVertex->Normal = ysMath::Constants::ZAxis;\n    centerVertex->Pos = ysMath::LoadVector(params.center_x, params.center_y);\n    centerVertex->TexCoord = ysVector2(0.5f, 0.5f);\n\n    const float angleStep = ysMath::Constants::TWO_PI / segmentCount;\n\n    for (int i = 0; i < segmentCount; ++i) {\n        const float angle0 = angleStep * i;\n        const float x = std::cosf(angle0);\n        const float y = std::sinf(angle0);\n\n        const float pos_x = params.center_x + x * params.radius;\n        const float pos_y = params.center_y + y * params.radius;\n\n        dbasic::Vertex *newVertex = writeVertex();\n        newVertex->Normal.Set(0, 0, 1, 0);\n        newVertex->Pos.Set(pos_x, pos_y, 0.0f, 1.0f);\n        newVertex->TexCoord.x = 0.5f * x + 0.5f;\n        newVertex->TexCoord.y = 0.5f * y + 0.5f;\n    }\n\n    for (int i = 0; i < segmentCount; ++i) {\n        writeFace(0, i + 1, 1 + ((i + 1) % segmentCount));\n    }\n\n    return true;\n}\n\nbool GeometryGenerator::generateCam(const Cam2dParameters &params) {\n    // edge_length = (sin(theta) * radius) * 2\n    // theta = arcsin(edge_length / (2 * radius))\n    // theta2 = PI - theta\n\n    startSubshape();\n\n    float angle = std::asinf(params.maxEdgeLength / (2 * params.baseRadius)) * 2;\n    angle = std::fminf(angle, ysMath::Constants::PI - params.smallestAngle);\n\n    const float steps = ysMath::Constants::TWO_PI / angle;\n\n    int segmentCount = (int)std::ceilf(steps);\n    segmentCount = (segmentCount < 3)\n        ? 3\n        : segmentCount;\n\n    const int vertexCount = 1 + segmentCount;\n    const int faceCount = segmentCount;\n    const int indexCount = faceCount * 3;\n\n    if (!checkCapacity(vertexCount, indexCount)) {\n        return false;\n    }\n\n    // Generate center vertex\n    dbasic::Vertex *centerVertex = writeVertex();\n    centerVertex->Normal = ysMath::Constants::ZAxis;\n    centerVertex->Pos = ysMath::LoadVector(params.center_x, params.center_y);\n    centerVertex->TexCoord = ysVector2(0.5f, 0.5f);\n\n    const float angleStep = ysMath::Constants::TWO_PI / segmentCount;\n    const float baseRollerPosition = params.baseRadius + params.rollerRadius;\n\n    float lastRoller_x = 0;\n    float lastRoller_y = 0;\n\n    float *positions_x = new float[segmentCount];\n    float *positions_y = new float[segmentCount];\n\n    for (int i = 0; i < segmentCount; ++i) {\n        const float angle0 = angleStep * i;\n        const float x = std::cosf(angle0 + ysMath::Constants::PI / 2);\n        const float y = std::sinf(angle0 + ysMath::Constants::PI / 2);\n\n        const float lift = (params.lift == nullptr)\n            ? 0.0f\n            : (float)params.lift->sampleTriangle((double)angle0 - ysMath::Constants::TWO_PI / 2);\n        const float rollerPosition = baseRollerPosition + lift;\n\n        const float rollerPos_x = params.center_x + x * rollerPosition;\n        const float rollerPos_y = params.center_y + y * rollerPosition;\n\n        float dx = -1, dy = 0;\n        if (i != 0) {\n            dx = rollerPos_x - lastRoller_x;\n            dy = rollerPos_y - lastRoller_y;\n\n            const float mag = std::sqrt(dx * dx + dy * dy);\n            dx /= mag;\n            dy /= mag;\n        }\n\n        lastRoller_x = rollerPos_x;\n        lastRoller_y = rollerPos_y;\n\n        const float t_dx = -dy;\n        const float t_dy = dx;\n\n        const float tangentPos_x = rollerPos_x + t_dx * params.rollerRadius;\n        const float tangentPos_y = rollerPos_y + t_dy * params.rollerRadius;\n\n        positions_x[i] = tangentPos_x;\n        positions_y[i] = tangentPos_y;\n    }\n\n    for (int i = 0; i < segmentCount; ++i) {\n        const int prev_i = (i - 1 + segmentCount) % segmentCount;\n        const int next_i = (i + 1) % segmentCount;\n\n        const float prev_x = positions_x[prev_i], prev_y = positions_y[prev_i];\n        const float next_x = positions_x[next_i], next_y = positions_y[next_i];\n\n        dbasic::Vertex *newVertex = writeVertex();\n        newVertex->Normal.Set(0, 0, 1, 0);\n        newVertex->Pos.Set((prev_x + next_x) / 2, (prev_y + next_y) / 2, 0.0f, 1.0f);\n        newVertex->TexCoord.x = 0.0f;\n        newVertex->TexCoord.y = 0.5f;\n    }\n\n    for (int i = 0; i < segmentCount; ++i) {\n        writeFace(0, i + 1, 1 + ((i + 1) % segmentCount));\n    }\n\n    delete[] positions_x;\n    delete[] positions_y;\n\n    return true;\n}\n\nbool GeometryGenerator::generateRhombus(const Rhombus2dParameters &params) {\n    startSubshape();\n\n    if (!checkCapacity(4, 6)) {\n        return false;\n    }\n\n    dbasic::Vertex *vertex;\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n            params.center_x + params.shear + params.width / 2,\n            params.center_y + params.height / 2);\n    vertex->TexCoord = ysVector2(1.0f, 1.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n            params.center_x + params.shear - params.width / 2,\n            params.center_y + params.height / 2);\n    vertex->TexCoord = ysVector2(0.0f, 1.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n        params.center_x - params.shear + params.width / 2,\n        params.center_y - params.height / 2);\n    vertex->TexCoord = ysVector2(1.0f, 0.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n            params.center_x - params.shear - params.width / 2,\n            params.center_y - params.height / 2);\n    vertex->TexCoord = ysVector2(0.0f, 0.0f);\n\n    writeFace(0, 1, 2);\n    writeFace(1, 3, 2);\n\n    return true;\n}\n\nbool GeometryGenerator::generateTrapezoid2d(const Trapezoid2dParameters &params) {\n    startSubshape();\n\n    if (!checkCapacity(3, 3)) {\n        return false;\n    }\n\n    dbasic::Vertex *vertex;\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n        params.center_x - params.base / 2, params.center_y - params.height / 2);\n    vertex->TexCoord = ysVector2(1.0f, 1.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n        params.center_x + params.base / 2, params.center_y - params.height / 2);\n    vertex->TexCoord = ysVector2(0.0f, 1.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n        params.center_x - params.top / 2, params.center_y + params.height / 2);\n    vertex->TexCoord = ysVector2(1.0f, 0.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n        params.center_x + params.top / 2, params.center_y + params.height / 2);\n    vertex->TexCoord = ysVector2(1.0f, 0.0f);\n\n    writeFace(0, 1, 2);\n    writeFace(1, 3, 2);\n\n    return true;\n}\n\nbool GeometryGenerator::generateIsoscelesTriangle(\n        float x,\n        float y,\n        float width,\n        float height)\n{\n    startSubshape();\n\n    if (!checkCapacity(3, 3)) {\n        return false;\n    }\n\n    dbasic::Vertex *vertex;\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(x - width / 2, y);\n    vertex->TexCoord = ysVector2(1.0f, 1.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(x + width / 2, y);\n    vertex->TexCoord = ysVector2(0.0f, 1.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(x, y + height);\n    vertex->TexCoord = ysVector2(1.0f, 0.0f);\n\n    writeFace(0, 1, 2);\n\n    return true;\n}\n\nbool GeometryGenerator::startPath(PathParameters &params) {\n    startSubshape();\n\n    const int n = params.n0 + params.n1;\n    if (n < 2) return true;\n\n    Point *p2 = (1 >= params.n0) ? params.p1 : params.p0;\n    const int i1 = (1 >= params.n0) ? 0 : 1;\n\n    const float dx = p2[i1].x - params.p0[0].x;\n    const float dy = p2[i1].y - params.p0[0].y;\n    const float length = std::sqrt(dx * dx + dy * dy);\n\n    const float dir_x = dx / length;\n    const float dir_y = dy / length;\n\n    const float perp_x = -dir_y;\n    const float perp_y = dir_x;\n\n    if (!checkCapacity(n * 2, (n - 1) * 6)) {\n        return false;\n    }\n\n    dbasic::Vertex *vertex;\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n        params.p0[0].x + perp_x * params.width / 2,\n        params.p0[0].y + perp_y * params.width / 2);\n    vertex->TexCoord = ysVector2(0.0f, 0.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n        params.p0[0].x - perp_x * params.width / 2,\n        params.p0[0].y - perp_y * params.width / 2);\n    vertex->TexCoord = ysVector2(0.0f, 0.0f);\n\n    params.v0 = 0;\n    params.v1 = 1;\n    params.pdir_x = dir_x;\n    params.pdir_y = dir_y;\n    params.perp_x = perp_x;\n    params.perp_y = perp_y;\n\n    return true;\n}\n\nbool GeometryGenerator::generatePathSegment(PathParameters &params, bool detached) {\n    const int n = params.n0 + params.n1;\n\n    if (params.i > n - 1) return true;\n\n    Point *p0 = (params.i >= params.n0) ? params.p1 : params.p0;\n    const int i0 = (params.i >= params.n0) ? (params.i - params.n0) : params.i;\n\n    if (params.i == n - 1) {\n        dbasic::Vertex *vertex;\n        vertex = writeVertex();\n        vertex->Normal = ysMath::Constants::ZAxis;\n        vertex->Pos = ysMath::LoadVector(\n            p0[i0].x + params.perp_x * params.width / 2,\n            p0[i0].y + params.perp_y * params.width / 2);\n        vertex->TexCoord = ysVector2(0.0f, 0.0f);\n\n        vertex = writeVertex();\n        vertex->Normal = ysMath::Constants::ZAxis;\n        vertex->Pos = ysMath::LoadVector(\n            p0[i0].x - params.perp_x * params.width / 2,\n            p0[i0].y - params.perp_y * params.width / 2);\n        vertex->TexCoord = ysVector2(0.0f, 0.0f);\n\n        if (!detached) {\n            writeFace(params.v0, params.v1, params.v1 + 1);\n            writeFace(params.v1, params.v1 + 2, params.v1 + 1);\n        }\n\n        return true;\n    }\n\n    Point *p1 = (params.i + 1 >= params.n0) ? params.p1 : params.p0;\n    const int i1 = (params.i + 1 >= params.n0) ? (params.i + 1 - params.n0) : params.i + 1;\n\n    const float dx1 = p1[i1].x - p0[i0].x;\n    const float dy1 = p1[i1].y - p0[i0].y;\n    const float length = std::sqrt(dx1 * dx1 + dy1 * dy1);\n\n    const float dir_x = dx1 / length;\n    const float dir_y = dy1 / length;\n\n    float perp_x = -dir_y - params.pdir_y;\n    float perp_y = dir_x + params.pdir_x;\n    float perp_l = std::sqrt(perp_x * perp_x + perp_y * perp_y);\n    if (perp_l == 0) {\n        perp_x = -dir_y;\n        perp_y = dir_x;\n    }\n    else {\n        perp_x /= perp_l;\n        perp_y /= perp_l;\n    }\n\n    dbasic::Vertex *vertex;\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n        p0[i0].x + perp_x * params.width / 2,\n        p0[i0].y + perp_y * params.width / 2);\n    vertex->TexCoord = ysVector2(0.0f, 0.0f);\n\n    vertex = writeVertex();\n    vertex->Normal = ysMath::Constants::ZAxis;\n    vertex->Pos = ysMath::LoadVector(\n        p0[i0].x - perp_x * params.width / 2,\n        p0[i0].y - perp_y * params.width / 2);\n    vertex->TexCoord = ysVector2(0.0f, 0.0f);\n\n    if (!detached) {\n        writeFace(params.v0, params.v1, params.v1 + 1);\n        writeFace(params.v1, params.v1 + 2, params.v1 + 1);\n    }\n\n    params.v0 = params.v1 + 1;\n    params.v1 = params.v1 + 2;\n    params.pdir_x = dir_x;\n    params.pdir_y = dir_y;\n    params.perp_x = perp_x;\n    params.perp_y = perp_y;\n\n    return true;\n}\n\ndbasic::Vertex *GeometryGenerator::writeVertex() {\n    return &m_vertexData[m_state.vertexPointer++];\n}\n\nvoid GeometryGenerator::startShape() {\n    m_state.currentShape.BaseIndex = m_state.indexPointer;\n    m_state.currentShape.BaseVertex = m_state.vertexPointer;\n    m_state.currentShape.FaceCount = 0;\n    m_state.currentShape.VertexData = &m_vertexData[m_state.vertexPointer];\n}\n\nvoid GeometryGenerator::endShape(GeometryIndices *indices) {\n    *indices = m_state.currentShape;\n}\n\nvoid GeometryGenerator::startSubshape() {\n    m_state.subshapeVertexPointer = m_state.vertexPointer - m_state.currentShape.BaseVertex;\n}\n\nvoid GeometryGenerator::writeFace(unsigned short i0, unsigned short i1, unsigned short i2) {\n    m_indexData[m_state.indexPointer + 0] = i0 + m_state.subshapeVertexPointer;\n    m_indexData[m_state.indexPointer + 1] = i1 + m_state.subshapeVertexPointer;\n    m_indexData[m_state.indexPointer + 2] = i2 + m_state.subshapeVertexPointer;\n\n    ++m_state.currentShape.FaceCount;\n\n    m_state.indexPointer += 3;\n}\n\nbool GeometryGenerator::checkCapacity(int vertexCount, int indexCount) {\n    return\n        (vertexCount + m_state.vertexPointer) <= m_vertexBufferSize &&\n        (indexCount + m_state.indexPointer) <= m_indexBufferSize;\n}\n\nysVector GeometryGenerator::findOrthogonal(const ysVector &v) {\n    ysVector base = ysMath::Constants::XAxis;\n    const float s = ysMath::GetScalar(ysMath::Dot(v, base));\n    if (abs(s) > 0.99f) {\n        base = ysMath::Constants::YAxis;\n    }\n\n    return ysMath::Normalize(ysMath::Cross(v, base));\n}\n"
  },
  {
    "path": "src/governor.cpp",
    "content": "#include \"../include/governor.h\"\n\n#include \"../include/engine.h\"\n#include \"../include/utilities.h\"\n\nGovernor::Governor() {\n    m_minSpeed = m_maxSpeed = 0;\n    m_targetSpeed = 0;\n    m_currentThrottle = 1.0;\n    m_velocity = 0.0;\n    m_minVelocity = m_maxVelocity = 0.0;\n    m_k_s = m_k_d = 0.0;\n    m_gamma = 1.0;\n}\n\nGovernor::~Governor() {\n    /* void */\n}\n\nvoid Governor::initialize(const Parameters &params) {\n    m_minSpeed = params.minSpeed;\n    m_maxSpeed = params.maxSpeed;\n    m_minVelocity = params.minVelocity;\n    m_maxVelocity = params.maxVelocity;\n    m_k_s = params.k_s;\n    m_k_d = params.k_d;\n    m_gamma = params.gamma;\n}\n\nvoid Governor::setSpeedControl(double s) {\n    Throttle::setSpeedControl(s);\n\n    m_targetSpeed = (1 - s) * m_minSpeed + s * m_maxSpeed;\n}\n\nvoid Governor::update(double dt, Engine *engine) {\n    const double currentSpeed = engine->getSpeed();\n    const double ds = m_targetSpeed * m_targetSpeed - currentSpeed * currentSpeed;\n\n    m_velocity += (dt * -ds * m_k_s - m_velocity * dt * m_k_d);\n    m_velocity = clamp(m_velocity, m_minVelocity, m_maxVelocity);\n   \n    if (std::abs(currentSpeed) < std::abs(0.5 * m_minSpeed)) {\n        m_velocity = 0;\n        m_currentThrottle = 1.0;\n    }\n\n    m_currentThrottle += m_velocity * dt;\n    m_currentThrottle = clamp(m_currentThrottle);\n\n    engine->setThrottle(1 - std::pow(1 - m_currentThrottle, m_gamma));\n}\n"
  },
  {
    "path": "src/ignition_module.cpp",
    "content": "#include \"../include/ignition_module.h\"\n\n#include \"../include/utilities.h\"\n#include \"../include/constants.h\"\n#include \"../include/units.h\"\n\n#include <cmath>\n\nIgnitionModule::IgnitionModule() {\n    m_plugs = nullptr;\n    m_crankshaft = nullptr;\n    m_timingCurve = nullptr;\n    m_cylinderCount = 0;\n    m_lastCrankshaftAngle = 0.0;\n    m_enabled = false;\n    m_revLimitTimer = 0.0;\n    m_revLimit = 0;\n    m_limiterDuration = 0;\n}\n\nIgnitionModule::~IgnitionModule() {\n    assert(m_plugs == nullptr);\n}\n\nvoid IgnitionModule::destroy() {\n    delete[] m_plugs;\n\n    m_plugs = nullptr;\n    m_cylinderCount = 0;\n}\n\nvoid IgnitionModule::initialize(const Parameters &params) {\n    m_cylinderCount = params.cylinderCount;\n    m_plugs = new SparkPlug[m_cylinderCount];\n    m_crankshaft = params.crankshaft;\n    m_timingCurve = params.timingCurve;\n    m_revLimit = params.revLimit;\n    m_limiterDuration = params.limiterDuration;\n}\n\nvoid IgnitionModule::setFiringOrder(int cylinderIndex, double angle) {\n    assert(cylinderIndex < m_cylinderCount);\n\n    m_plugs[cylinderIndex].angle = angle;\n    m_plugs[cylinderIndex].enabled = true;\n}\n\nvoid IgnitionModule::reset() {\n    m_lastCrankshaftAngle = m_crankshaft->getCycleAngle();\n    resetIgnitionEvents();\n}\n\nvoid IgnitionModule::update(double dt) {\n    const double cycleAngle = m_crankshaft->getCycleAngle();\n\n    if (m_enabled && m_revLimitTimer == 0) {\n        const double fourPi = 4 * constants::pi;\n        const double advance = getTimingAdvance();\n\n        for (int i = 0; i < m_cylinderCount; ++i) {\n            double adjustedAngle = positiveMod(m_plugs[i].angle - advance, fourPi);\n            const double r0 = m_lastCrankshaftAngle;\n            double r1 = cycleAngle;\n\n            if (m_crankshaft->m_body.v_theta < 0) {\n                if (r1 < r0) {\n                    r1 += fourPi;\n                    adjustedAngle += fourPi;\n                }\n\n                if (adjustedAngle >= r0 && adjustedAngle < r1) {\n                    m_plugs[i].ignitionEvent = m_plugs[i].enabled;\n                }\n            }\n            else {\n                if (r1 > r0) {\n                    r1 -= fourPi;\n                    adjustedAngle -= fourPi;\n                }\n\n                if (adjustedAngle >= r1 && adjustedAngle < r0) {\n                    m_plugs[i].ignitionEvent = m_plugs[i].enabled;\n                }\n            }\n        }\n    }\n\n    m_revLimitTimer -= dt;\n    if (std::fabs(m_crankshaft->m_body.v_theta) > m_revLimit) {\n        m_revLimitTimer = m_limiterDuration;\n    }\n\n    if (m_revLimitTimer < 0) {\n        m_revLimitTimer = 0;\n    }\n\n    m_lastCrankshaftAngle = cycleAngle;\n}\n\nbool IgnitionModule::getIgnitionEvent(int index) const {\n    return m_plugs[index].ignitionEvent;\n}\n\nvoid IgnitionModule::resetIgnitionEvents() {\n    for (int i = 0; i < m_cylinderCount; ++i) {\n        m_plugs[i].ignitionEvent = false;\n    }\n}\n\ndouble IgnitionModule::getTimingAdvance() {\n    return m_timingCurve->sampleTriangle(-m_crankshaft->m_body.v_theta);\n}\n\nIgnitionModule::SparkPlug *IgnitionModule::getPlug(int i) {\n    return &m_plugs[((i % m_cylinderCount) + m_cylinderCount) % m_cylinderCount];\n}\n"
  },
  {
    "path": "src/impulse_response.cpp",
    "content": "#include \"../include/impulse_response.h\"\n\nImpulseResponse::ImpulseResponse() {\n    m_volume = 1.0;\n}\n\nImpulseResponse::~ImpulseResponse() {\n    /* void */\n}\n\nvoid ImpulseResponse::initialize(\n    const std::string &filename,\n    double volume)\n{\n    m_filename = filename;\n    m_volume = volume;\n}\n"
  },
  {
    "path": "src/info_cluster.cpp",
    "content": "#include \"../include/info_cluster.h\"\n\n#include \"../include/engine_sim_application.h\"\n\n#include <sstream>\n#include <iomanip>\n\nInfoCluster::InfoCluster() {\n    m_engine = nullptr;\n    m_logMessage = \"Started\";\n}\n\nInfoCluster::~InfoCluster() {\n    /* void */\n}\n\nvoid InfoCluster::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n}\n\nvoid InfoCluster::destroy() {\n    UiElement::destroy();\n}\n\nvoid InfoCluster::update(float dt) {\n    UiElement::update(dt);\n}\n\nvoid InfoCluster::render() {\n    Grid grid;\n    grid.h_cells = 6;\n    grid.v_cells = 4;\n\n    const Bounds logoBounds = grid.get(m_bounds, 0, 0, 1, 2);\n    drawFrame(logoBounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor());\n\n    drawModel(\n        m_app->getAssetManager()->GetModelAsset(\"Logo\"),\n        m_app->getForegroundColor(),\n        logoBounds.getPosition(Bounds::center),\n        Point(logoBounds.height(), logoBounds.height()) * 0.75f);\n\n    const Bounds titleBounds = grid.get(m_bounds, 1, 0, 5, 2);\n    drawFrame(titleBounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor());\n\n    Grid titleSplit;\n    titleSplit.h_cells = 1;\n    titleSplit.v_cells = 3;\n    drawAlignedText(\n        \"ENGINE SIMULATOR\",\n        titleSplit.get(titleBounds, 0, 0).inset(10.0f).move({ 0.0f, -21.0f }),\n        42.0f,\n        Bounds::bl,\n        Bounds::bl);\n    drawAlignedText(\n        \"YOUTUBE/ANGETHEGREAT\",\n        titleSplit.get(titleBounds, 0, 1).inset(10.0f).move({ 0.0f, 5.0f }),\n        24.0f,\n        Bounds::tl,\n        Bounds::tl);\n    drawAlignedText(\n        \"BUILD: v\" + EngineSimApplication::getBuildVersion() + \" // \" __DATE__,\n        titleSplit.get(titleBounds, 0, 2).inset(10.0f).move({ 0.0f, 10.0f }),\n        16.0f,\n        Bounds::tl,\n        Bounds::tl);\n\n    const Bounds engineInfoBounds = grid.get(m_bounds, 0, 2, 6, 1);\n    drawFrame(engineInfoBounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor());\n\n    drawAlignedText(\n        (m_engine != nullptr) ? m_engine->getName() : \"<NO ENGINE>\",\n        engineInfoBounds.inset(10.0f),\n        24.0f,\n        Bounds::lm,\n        Bounds::lm);\n\n    std::stringstream ss;\n    if (m_engine != nullptr) {\n        ss << std::fixed;\n\n        if (m_engine->getDisplacement() < units::volume(1.0, units::L)) {\n            ss << std::setprecision(0) << units::convert(m_engine->getDisplacement(), units::cc) << \" cc -- \";\n        }\n        else {\n            ss << std::setprecision(1) << units::convert(m_engine->getDisplacement(), units::L) << \" L -- \";\n        }\n\n        ss << std::setprecision(0) << units::convert(m_engine->getDisplacement(), units::cubic_inches) << \" CI\";\n    }\n    else {\n        ss << \"N/A\";\n    }\n\n    drawAlignedText(\n        ss.str(),\n        engineInfoBounds.inset(10.0f),\n        24.0f,\n        Bounds::rm,\n        Bounds::rm);\n\n    const Bounds infoMessagesBounds = grid.get(m_bounds, 0, 3, 6, 1);\n    drawFrame(infoMessagesBounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor());\n\n    drawAlignedText(\n        m_logMessage,\n        infoMessagesBounds.inset(10.0f),\n        24.0f,\n        Bounds::lm,\n        Bounds::lm);\n}\n"
  },
  {
    "path": "src/intake.cpp",
    "content": "#include \"../include/intake.h\"\n\n#include \"../include/units.h\"\n\n#include <cmath>\n\nIntake::Intake() {\n    m_inputFlowK = 0;\n    m_idleFlowK = 0;\n    m_flow = 0;\n    m_throttle = 1.0;\n    m_idleThrottlePlatePosition = 0.0;\n    m_crossSectionArea = 0.0;\n    m_flowRate = 0;\n    m_totalFuelInjected = 0;\n    m_molecularAfr = 0;\n    m_runnerLength = 0;\n}\n\nIntake::~Intake() {\n    /* void */\n}\n\nvoid Intake::initialize(Parameters &params) {\n    const double width = std::sqrt(params.CrossSectionArea);\n    m_system.initialize(\n        units::pressure(1.0, units::atm),\n        params.volume,\n        units::celcius(25.0));\n    m_system.setGeometry(\n        width,\n        params.volume / params.CrossSectionArea,\n        1.0,\n        0.0);\n\n    m_atmosphere.initialize(\n        units::pressure(1.0, units::atm),\n        units::volume(1000.0, units::m3),\n        units::celcius(25.0));\n    m_atmosphere.setGeometry(\n        units::distance(100.0, units::m),\n        units::distance(100.0, units::m),\n        1.0,\n        0.0);\n\n    m_inputFlowK = params.InputFlowK;\n    m_molecularAfr = params.MolecularAfr;\n    m_idleFlowK = params.IdleFlowK;\n    m_idleThrottlePlatePosition = params.IdleThrottlePlatePosition;\n    m_runnerLength = params.RunnerLength;\n    m_crossSectionArea = params.CrossSectionArea;\n    m_velocityDecay = params.VelocityDecay;\n    m_runnerFlowRate = params.RunnerFlowRate;\n}\n\nvoid Intake::destroy() {\n    /* void */\n}\n\nvoid Intake::process(double dt) {\n    const double ideal_afr = 0.8 * m_molecularAfr * 4;\n    const double current_afr = (m_system.mix().p_o2 + m_system.mix().p_inert) / m_system.mix().p_fuel;\n\n    const double p_air = ideal_afr / (1 + ideal_afr);\n    GasSystem::Mix fuelAirMix;\n    fuelAirMix.p_fuel = 1 - p_air;\n    fuelAirMix.p_inert = p_air * 0.75;\n    fuelAirMix.p_o2 = p_air * 0.25;\n\n    const double idle_afr = 2.0;\n    const double p_idle_air = idle_afr / (1 + idle_afr);\n    GasSystem::Mix fuelMix;\n    fuelMix.p_fuel = (1.0 - p_idle_air);\n    fuelMix.p_inert = p_idle_air * 0.75;\n    fuelMix.p_o2 = p_idle_air * 0.25;\n\n    const double throttle = getThrottlePlatePosition();\n    const double flowAttenuation = std::cos(throttle * constants::pi / 2);\n\n    GasSystem::FlowParameters flowParams;\n    flowParams.crossSectionArea_0 = units::area(10, units::m2);\n    flowParams.crossSectionArea_1 = m_crossSectionArea;\n    flowParams.direction_x = 0.0;\n    flowParams.direction_y = -1.0;\n    flowParams.dt = dt;\n\n    m_atmosphere.reset(units::pressure(1.0, units::atm), units::celcius(25.0), fuelAirMix);\n    flowParams.system_0 = &m_atmosphere;\n    flowParams.system_1 = &m_system;\n    flowParams.k_flow = flowAttenuation * m_inputFlowK;\n    m_flow = m_system.flow(flowParams);\n\n    m_atmosphere.reset(units::pressure(1.0, units::atm), units::celcius(25.0), fuelMix);\n    flowParams.system_0 = &m_atmosphere;\n    flowParams.system_1 = &m_system;\n    flowParams.k_flow = m_idleFlowK;\n    const double idleCircuitFlow = m_system.flow(flowParams);\n\n    m_system.dissipateExcessVelocity();\n    m_system.updateVelocity(dt, m_velocityDecay);\n\n    if (m_flow > 0) {\n        m_totalFuelInjected += fuelAirMix.p_fuel * m_flow;\n    }\n\n    if (idleCircuitFlow > 0) {\n        m_totalFuelInjected += fuelMix.p_fuel * idleCircuitFlow;\n    }\n}\n"
  },
  {
    "path": "src/jitter_filter.cpp",
    "content": "#include \"../include/jitter_filter.h\"\n\nJitterFilter::JitterFilter() {\n    m_history = nullptr;\n    m_maxJitter = 0;\n    m_offset = 0;\n    m_jitterScale = 0.0f;\n}\n\nJitterFilter::~JitterFilter() {\n    /* void */\n}\n\nvoid JitterFilter::initialize(\n    int maxJitter,\n    float cutoffFrequency,\n    float audioFrequency)\n{\n    m_maxJitter = maxJitter;\n\n    m_history = new float[maxJitter];\n    m_offset = 0;\n    memset(m_history, 0, sizeof(float) * maxJitter);\n\n    m_noiseFilter.setCutoffFrequency(cutoffFrequency, audioFrequency);\n}\n\nfloat JitterFilter::f(float sample) {\n    return fast_f(sample);\n}\n"
  },
  {
    "path": "src/labeled_gauge.cpp",
    "content": "#include \"../include/labeled_gauge.h\"\n\n#include \"../include/engine_sim_application.h\"\n\n#include <sstream>\n#include <iomanip>\n\nLabeledGauge::LabeledGauge() {\n    m_gauge = nullptr;\n    m_title = \"\";\n    m_precision = 2;\n    m_unit = \"\";\n    m_spaceBeforeUnit = true;\n}\n\nLabeledGauge::~LabeledGauge() {\n    /* void */\n}\n\nvoid LabeledGauge::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n\n    m_gauge = addElement<Gauge>();\n    m_gauge->m_center = { 0.0f, -20.0f };\n}\n\nvoid LabeledGauge::destroy() {\n    UiElement::destroy();\n}\n\nvoid LabeledGauge::update(float dt) {\n    UiElement::update(dt);\n}\n\nvoid LabeledGauge::render() {\n    drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor());\n\n    const Bounds bounds = m_bounds.inset(m_margin);\n    const Bounds title = bounds.verticalSplit(1.0f, 0.9f);\n    const Bounds gaugeBounds = bounds.verticalSplit(0.0f, 0.9f);\n\n    drawCenteredText(m_title, title.inset(10.0f), 24.0f);\n\n    const double value = m_gauge->m_value;\n\n    std::stringstream ss;\n    ss << std::fixed << std::setprecision(m_precision);\n    ss << value << ((m_spaceBeforeUnit && m_unit.length() > 0) ? \" \" : \"\") << m_unit;\n    drawAlignedText(\n        ss.str(),\n        gaugeBounds.verticalSplit(0.0f, 2 / 8.0f), \n        gaugeBounds.height() / 8,\n        Bounds::bm,\n        Bounds::bm);\n\n    m_gauge->m_bounds = gaugeBounds;\n    m_gauge->setLocalPosition({ 0, 0 });\n    m_gauge->m_outerRadius = std::fmin(gaugeBounds.width(), gaugeBounds.height()) / 2.0f;\n    m_gauge->m_needleOuterRadius = m_gauge->m_outerRadius * m_needleOuterRadius;\n    m_gauge->m_needleInnerRadius = -m_gauge->m_outerRadius * m_needleInnerRadius;\n\n    UiElement::render();\n}\n"
  },
  {
    "path": "src/leveling_filter.cpp",
    "content": "#include \"../include/leveling_filter.h\"\n\n#include <cmath>\n\nLevelingFilter::LevelingFilter() {\n    m_peak = 30000.0;\n    m_attenuation = 1.0;\n    p_target = 30000.0;\n    p_minLevel = 0.0;\n    p_maxLevel = 1.0;\n}\n\nLevelingFilter::~LevelingFilter() {\n    /* void */\n}\n\nfloat LevelingFilter::f(float sample) {\n    m_peak = 0.999f * m_peak;\n    if (std::abs(sample) > m_peak) {\n        m_peak = std::abs(sample);\n    }\n\n    if (m_peak == 0) return 0;\n\n    const float raw_attenuation = p_target / m_peak;\n\n    float attenuation = raw_attenuation;\n    if (attenuation < p_minLevel) attenuation = p_minLevel;\n    else if (attenuation > p_maxLevel) attenuation = p_maxLevel;\n\n    m_attenuation = 0.9 * m_attenuation + 0.1 * attenuation;\n\n    return sample * m_attenuation;\n}\n"
  },
  {
    "path": "src/load_simulation_cluster.cpp",
    "content": "#include \"../include/load_simulation_cluster.h\"\n\n#include \"../include/engine_sim_application.h\"\n#include \"../include/ui_utilities.h\"\n\n#include <sstream>\n#include <iomanip>\n\nLoadSimulationCluster::LoadSimulationCluster() {\n    m_torqueGauge = nullptr;\n    m_hpGauge = nullptr;\n    m_simulator = nullptr;\n    m_clutchPressureGauge = nullptr;\n    m_filteredHorsepower = 0.0;\n    m_filteredTorque = 0.0;\n    m_peakHorsepower = 0.0;\n    m_peakTorque = 0.0;\n    m_peakHorsepowerRpm = 0.0;\n    m_peakTorqueRpm = 0.0;\n    memset(m_systemStatusLights, 0, sizeof(double) * 4);\n}\n\nLoadSimulationCluster::~LoadSimulationCluster() {\n    /* void */\n}\n\nvoid LoadSimulationCluster::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n\n    constexpr float shortenAngle = (float)units::angle(1.0, units::deg);\n\n    m_dynoSpeedGauge = addElement<LabeledGauge>();\n    m_dynoSpeedGauge->m_title = \"DYNO. SPEED\";\n    m_dynoSpeedGauge->m_unit = \"RPM\";\n    m_dynoSpeedGauge->m_precision = 0;\n    m_dynoSpeedGauge->setLocalPosition({ 0, 0 });\n    m_dynoSpeedGauge->m_gauge->m_min = 0;\n    m_dynoSpeedGauge->m_gauge->m_max = 100;\n    m_dynoSpeedGauge->m_gauge->m_minorStep = 500;\n    m_dynoSpeedGauge->m_gauge->m_majorStep = 1000;\n    m_dynoSpeedGauge->m_gauge->m_maxMinorTick = INT_MAX;\n    m_dynoSpeedGauge->m_gauge->m_thetaMin = (float)constants::pi * 0.8f;\n    m_dynoSpeedGauge->m_gauge->m_thetaMax = (float)constants::pi * 0.2f;\n    m_dynoSpeedGauge->m_gauge->m_needleWidth = 4.0f;\n    m_dynoSpeedGauge->m_gauge->m_gamma = 1.0f;\n    m_dynoSpeedGauge->m_gauge->m_needleKs = 1000.0f;\n    m_dynoSpeedGauge->m_gauge->m_needleKd = 5.0f;\n    m_dynoSpeedGauge->m_gauge->setBandCount(0);\n\n    m_torqueGauge = addElement<LabeledGauge>();\n    m_torqueGauge->m_title = \"TORQUE\";\n    m_torqueGauge->m_unit = \"LB-FT\";\n    m_torqueGauge->m_precision = 0;\n    m_torqueGauge->setLocalPosition({ 0, 0 });\n    m_torqueGauge->m_gauge->m_min = 0;\n    m_torqueGauge->m_gauge->m_max = 1000;\n    m_torqueGauge->m_gauge->m_minorStep = 50;\n    m_torqueGauge->m_gauge->m_majorStep = 100;\n    m_torqueGauge->m_gauge->m_maxMinorTick = INT_MAX;\n    m_torqueGauge->m_gauge->m_thetaMin = (float)constants::pi * 0.8f;\n    m_torqueGauge->m_gauge->m_thetaMax = (float)constants::pi * 0.2f;\n    m_torqueGauge->m_gauge->m_needleWidth = 4.0f;\n    m_torqueGauge->m_gauge->m_gamma = 1.0f;\n    m_torqueGauge->m_gauge->m_needleKs = 1000.0f;\n    m_torqueGauge->m_gauge->m_needleKd = 5.0f;\n    m_torqueGauge->m_gauge->setBandCount(0);\n\n    m_hpGauge = addElement<LabeledGauge>();\n    m_hpGauge->m_title = \"POWER\";\n    m_hpGauge->m_unit = \"HP\";\n    m_hpGauge->m_precision = 0;\n    m_hpGauge->setLocalPosition({ 0, 0 });\n    m_hpGauge->m_gauge->m_min = 0;\n    m_hpGauge->m_gauge->m_max = 1000;\n    m_hpGauge->m_gauge->m_minorStep = 50;\n    m_hpGauge->m_gauge->m_majorStep = 100;\n    m_hpGauge->m_gauge->m_maxMinorTick = INT_MAX;\n    m_hpGauge->m_gauge->m_thetaMin = (float)constants::pi * 0.8f;\n    m_hpGauge->m_gauge->m_thetaMax = (float)constants::pi * 0.2f;\n    m_hpGauge->m_gauge->m_needleWidth = 4.0f;\n    m_hpGauge->m_gauge->m_gamma = 1.0f;\n    m_hpGauge->m_gauge->m_needleKs = 1000.0f;\n    m_hpGauge->m_gauge->m_needleKd = 5.0f;\n    m_hpGauge->m_gauge->setBandCount(0);\n\n    m_clutchPressureGauge = addElement<LabeledGauge>();\n    m_clutchPressureGauge->m_title = \"CLUTCH\";\n    m_clutchPressureGauge->m_unit = \"\";\n    m_clutchPressureGauge->m_spaceBeforeUnit = false;\n    m_clutchPressureGauge->m_precision = 0;\n    m_clutchPressureGauge->setLocalPosition({ 0, 0 });\n    m_clutchPressureGauge->m_gauge->m_min = 0;\n    m_clutchPressureGauge->m_gauge->m_max = 100;\n    m_clutchPressureGauge->m_gauge->m_minorStep = 10;\n    m_clutchPressureGauge->m_gauge->m_majorStep = 50;\n    m_clutchPressureGauge->m_gauge->m_maxMinorTick = 200;\n    m_clutchPressureGauge->m_gauge->m_thetaMin = (float)constants::pi * 0.8f;\n    m_clutchPressureGauge->m_gauge->m_thetaMax = (float)constants::pi * 0.2f;\n    m_clutchPressureGauge->m_gauge->m_needleWidth = 4.0f;\n    m_clutchPressureGauge->m_gauge->m_gamma = 1.0f;\n    m_clutchPressureGauge->m_gauge->m_needleKs = 1000.0f;\n    m_clutchPressureGauge->m_gauge->m_needleKd = 5.0f;\n    m_clutchPressureGauge->m_gauge->m_needleMaxVelocity = 10.0f;\n    m_clutchPressureGauge->m_gauge->setBandCount(0);\n\n    m_torqueUnits = app->getAppSettings()->torqueUnits;\n    m_powerUnits = app->getAppSettings()->powerUnits;\n    setUnits();\n}\n\nvoid LoadSimulationCluster::destroy() {\n    /* void */\n}\n\nvoid LoadSimulationCluster::update(float dt) {\n    UiElement::update(dt);\n\n    const float systemStatuses[] = {\n        isIgnitionOn() ? 1.0f : 0.01f,\n        m_simulator->m_starterMotor.m_enabled ? 1.0f : 0.01f,\n        m_simulator->m_dyno.m_enabled ? 1.0f : 0.01f,\n        m_simulator->m_dyno.m_hold ? (m_simulator->m_dyno.m_enabled ? 1.0f : 0.25f) : 0.01f\n    };\n\n    constexpr float RC = 0.08f;\n    const float alpha = dt / (dt + RC);\n\n    for (int i = 0; i < 4; ++i) {\n        const float next = systemStatuses[i];\n        m_systemStatusLights[i] = (1 - alpha) * m_systemStatusLights[i] + alpha * next;\n    }\n\n    if (m_app->getEngine()->ProcessKeyDown(ysKey::Code::I)) {\n        std::stringstream ss;\n        ss << std::setprecision(0) << std::fixed;\n        if (m_powerUnits == \"hp\") {\n            ss << m_peakHorsepower << \"hp @ \" << m_peakHorsepowerRpm << \"rpm\";\n        }\n        else {\n            ss << m_peakHorsepower << \"kW @ \" << m_peakHorsepowerRpm << \"rpm\";\n        }\n\n        ss << \" | \";\n\n        if (m_torqueUnits == \"lb-ft\") {\n            ss << m_peakTorque << \"lb-ft @ \" << m_peakTorqueRpm << \"rpm\";\n        }\n        else {\n            ss << m_peakTorque << \"Nm @ \" << m_peakTorqueRpm << \"rpm\";\n        }\n        m_app->getInfoCluster()->setLogMessage(ss.str());\n    }\n\n    updateHpAndTorque(dt);\n}\n\nvoid LoadSimulationCluster::render() {\n    Grid grid;\n    grid.h_cells = 3;\n    grid.v_cells = 2;\n\n    const Bounds gearBounds = grid.get(m_bounds, 2, 0);\n    drawCurrentGear(gearBounds);\n\n    const Bounds clutchBounds = grid.get(m_bounds, 1, 0);\n    drawClutchPressureGauge(clutchBounds);\n\n    const Bounds systemStatusBounds = grid.get(m_bounds, 0, 0);\n    drawSystemStatus(systemStatusBounds);\n\n    const Bounds dynoSpeedBounds = grid.get(m_bounds, 0, 1);\n    m_dynoSpeedGauge->m_gauge->m_value = \n       (float)units::toRpm(std::abs(m_simulator->m_dyno.m_rotationSpeed));\n    m_dynoSpeedGauge->m_bounds = dynoSpeedBounds;\n\n    Engine *engine = m_simulator->getEngine();\n\n    constexpr float shortenAngle = (float)units::angle(1.0, units::deg);\n    const double redline = units::toRpm((engine != nullptr) ? engine->getRedline() : 0);\n    const double maxRpm = std::floor(redline / 500.0) * 500.0;\n    m_dynoSpeedGauge->m_gauge->m_max = (int)(maxRpm);\n    m_dynoSpeedGauge->m_gauge->setBandCount(1);\n    m_dynoSpeedGauge->m_gauge->setBand(\n        { m_app->getRed(), (float)redline, (float)maxRpm, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 0);\n\n    const Bounds torqueBounds = grid.get(m_bounds, 1, 1);\n    m_torqueGauge->m_gauge->m_value = m_simulator->m_dyno.m_enabled\n        ? (float)m_filteredTorque\n        : (float)m_peakTorque;\n    m_torqueGauge->m_bounds = torqueBounds;\n\n    const Bounds horsepowerBounds = grid.get(m_bounds, 2, 1);\n    m_hpGauge->m_gauge->m_value = m_simulator->m_dyno.m_enabled\n        ? (float)m_filteredHorsepower\n        : (float)m_peakHorsepower;\n    m_hpGauge->m_bounds = horsepowerBounds;\n\n    UiElement::render();\n}\n\nvoid LoadSimulationCluster::drawCurrentGear(const Bounds &bounds) {\n    const Bounds insetBounds = bounds.inset(10.0f);\n    const Bounds title = insetBounds.verticalSplit(0.9f, 1.0f);\n    const Bounds body = insetBounds.verticalSplit(0.0f, 0.9f);\n\n    drawFrame(bounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor());\n    drawCenteredText(\"Gear\", title.inset(10.0f), 24.0f);\n\n    const int gear = (getTransmission() != nullptr)\n        ? getTransmission()->getGear()\n        : -1;\n    std::stringstream ss;\n    \n    if (gear != -1) ss << (gear + 1);\n    else ss << \"N\";\n\n    drawCenteredText(ss.str(), body, 64.0f, Bounds::center);\n}\n\nvoid LoadSimulationCluster::drawClutchPressureGauge(const Bounds &bounds) {\n    m_clutchPressureGauge->m_bounds = bounds;\n    m_clutchPressureGauge->m_gauge->m_value = (getTransmission() != nullptr)\n        ? (float)getTransmission()->getClutchPressure() * 100.0f\n        : 0.0f;\n}\n\nvoid LoadSimulationCluster::drawSystemStatus(const Bounds &bounds) {\n    const Bounds left = bounds.horizontalSplit(0.0f, 0.6f);\n    const Bounds right = bounds.horizontalSplit(0.6f, 1.0f);\n\n    drawFrame(bounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor());\n\n    Grid grid;\n    grid.v_cells = 4;\n    grid.h_cells = 1;\n\n    drawText(\n        \"Ignition\",\n        grid.get(left, 0, 0).inset(10.0f),\n        20.0,\n        Bounds::lm);\n    drawText(\n        \"Starter\",\n        grid.get(left, 0, 1).inset(10.0f),\n        20.0,\n        Bounds::lm);\n    drawText(\n        \"Dyno.\",\n        grid.get(left, 0, 2).inset(10.0f),\n        20.0,\n        Bounds::lm);\n    drawText(\n        \"Hold\",\n        grid.get(left, 0, 3).inset(10.0f),\n        20.0,\n        Bounds::lm);\n\n    for (int i = 0; i < 4; ++i) {\n        const Bounds rawBounds = grid.get(right, 0, i);\n        const float width = std::fmax(rawBounds.width(), rawBounds.height());\n        const Bounds squareBounds(width - 20.0f, 5.0f, rawBounds.getPosition(Bounds::center), Bounds::center);\n\n        drawFrame(\n            squareBounds,\n            1.0f,\n            mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.001f),\n            mix(m_app->getBackgroundColor(), m_app->getRed(), m_systemStatusLights[i])\n        );\n    }\n}\n\nvoid LoadSimulationCluster::updateHpAndTorque(float dt) {\n    constexpr double RC = 0.1;\n    const double alpha = dt / (dt + RC);\n\n    const double torque = m_simulator->getFilteredDynoTorque();\n    const double power = m_simulator->getDynoPower();\n    const double torqueWithUnits = (m_torqueUnits == \"Nm\")\n        ? (units::convert(torque, units::Nm))\n        : (units::convert(torque, units::ft_lb));\n    const double powerWithUnits = (m_powerUnits == \"kW\")\n        ? (units::convert(power, units::kW))\n        : (units::convert(power, units::hp));\n\n    m_filteredTorque = (1 - alpha) * m_filteredTorque + alpha * torqueWithUnits;\n    m_filteredHorsepower = (1 - alpha) * m_filteredHorsepower + alpha * powerWithUnits;\n\n    if (m_simulator->getEngine() != nullptr) {\n        if (m_filteredTorque > m_peakTorque) {\n            m_peakTorque = m_filteredTorque;\n            m_peakTorqueRpm = m_simulator->getEngine()->getRpm();\n        }\n\n        if (m_filteredHorsepower > m_peakHorsepower) {\n            m_peakHorsepower = std::fmax(m_peakHorsepower, m_filteredHorsepower);\n            m_peakHorsepowerRpm = m_simulator->getEngine()->getRpm();\n        }\n    }\n}\n\nbool LoadSimulationCluster::isIgnitionOn() const {\n    Engine *engine = m_simulator->getEngine();\n    return (engine != nullptr)\n        ? engine->getIgnitionModule()->m_enabled\n        : false;\n}\n\nvoid LoadSimulationCluster::setUnits(){\n    if (m_torqueUnits == \"lb-ft\") {\n        m_torqueGauge->m_unit = \"lb-ft\";\n        m_torqueGauge->m_precision = 0;\n        m_torqueGauge->m_gauge->m_min = 0;\n        m_torqueGauge->m_gauge->m_max = 1000;\n        m_torqueGauge->m_gauge->m_minorStep = 50;\n        m_torqueGauge->m_gauge->m_majorStep = 100;\n    }\n    else if (m_torqueUnits == \"Nm\") {\n        m_torqueGauge->m_unit = \"Nm\";\n        m_torqueGauge->m_precision = 1;\n        m_torqueGauge->m_gauge->m_min = 0;\n        m_torqueGauge->m_gauge->m_max = 1000;\n        m_torqueGauge->m_gauge->m_minorStep = 50;\n        m_torqueGauge->m_gauge->m_majorStep = 100;\n    }\n\n    if (m_powerUnits == \"hp\") {\n        m_hpGauge->m_unit = \"hp\";\n        m_hpGauge->m_precision = 0;\n\n        m_hpGauge->m_gauge->m_min = 0;\n        m_hpGauge->m_gauge->m_max = 1000;\n        m_hpGauge->m_gauge->m_minorStep = 50;\n        m_hpGauge->m_gauge->m_majorStep = 100;\n    }\n    else if (m_powerUnits == \"kW\") {\n        m_hpGauge->m_unit = \"kW\";\n        m_hpGauge->m_precision = 1;\n        m_hpGauge->m_gauge->m_min = 0;\n        m_hpGauge->m_gauge->m_max = 1000;\n        m_hpGauge->m_gauge->m_minorStep = 50;\n        m_hpGauge->m_gauge->m_majorStep = 100;\n    }\n}\n"
  },
  {
    "path": "src/low_pass_filter.cpp",
    "content": "#include \"../include/low_pass_filter.h\"\n\nLowPassFilter::LowPassFilter() {\n    m_y = 0;\n    m_rc = 0;\n    m_dt = 1 / 44100.0f;\n}\n\nLowPassFilter::~LowPassFilter() {\n    /* void */\n}\n\nfloat LowPassFilter::f(float sample) {\n    return fast_f(sample);\n}\n"
  },
  {
    "path": "src/main.cpp",
    "content": "#include \"../include/engine_sim_application.h\"\n\n#include <iostream>\n\nint WINAPI WinMain(\n    _In_ HINSTANCE hInstance,\n    _In_opt_ HINSTANCE hPrevInstance,\n    _In_ LPSTR lpCmdLine,\n    _In_ int nCmdShow)\n{\n    (void)nCmdShow;\n    (void)lpCmdLine;\n    (void)hPrevInstance;\n\n    EngineSimApplication application;\n    application.initialize((void *)&hInstance, ysContextObject::DeviceAPI::DirectX11);\n    application.run();\n    application.destroy();\n\n    return 0;\n}\n"
  },
  {
    "path": "src/mixer_cluster.cpp",
    "content": "#include \"../include/mixer_cluster.h\"\n\n#include \"../include/units.h\"\n#include \"../include/gauge.h\"\n#include \"../include/constants.h\"\n#include \"../include/engine_sim_application.h\"\n\n#include <sstream>\n\nMixerCluster::MixerCluster() {\n    m_volumeGauge = nullptr,\n    m_convolutionGauge = nullptr,\n    m_highFreqFilterGauge = nullptr,\n    m_levelerGauge = nullptr;\n    m_noise0Gauge = nullptr;\n    m_noise1Gauge = nullptr;\n    m_simulator = nullptr;\n}\n\nMixerCluster::~MixerCluster() {\n    /* void */\n}\n\nvoid MixerCluster::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n\n    constexpr float shortenAngle = (float)units::angle(1.0, units::deg);\n\n    m_volumeGauge = addElement<LabeledGauge>();\n    m_volumeGauge->m_title = \"Vol.\";\n    m_volumeGauge->m_unit = \"\";\n    m_volumeGauge->m_precision = 1;\n    m_volumeGauge->setLocalPosition({ 0, 0 });\n    m_volumeGauge->m_gauge->m_min = 0;\n    m_volumeGauge->m_gauge->m_max = 100;\n    m_volumeGauge->m_gauge->m_minorStep = 5;\n    m_volumeGauge->m_gauge->m_majorStep = 10;\n    m_volumeGauge->m_gauge->m_maxMinorTick = 1000000;\n    m_volumeGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_volumeGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_volumeGauge->m_gauge->m_needleWidth = 4.0f;\n    m_volumeGauge->m_gauge->m_gamma = 1.0f;\n    m_volumeGauge->m_gauge->m_needleKs = 1000.0f;\n    m_volumeGauge->m_gauge->m_needleKd = 20.0f;\n    m_volumeGauge->m_gauge->setBandCount(0);\n\n    m_levelerGauge = addElement<LabeledGauge>();\n    m_levelerGauge->m_title = \"Lvl.\";\n    m_levelerGauge->m_unit = \"\";\n    m_levelerGauge->m_precision = 1;\n    m_levelerGauge->setLocalPosition({ 0, 0 });\n    m_levelerGauge->m_gauge->m_min = 0;\n    m_levelerGauge->m_gauge->m_max = 100;\n    m_levelerGauge->m_gauge->m_minorStep = 5;\n    m_levelerGauge->m_gauge->m_majorStep = 10;\n    m_levelerGauge->m_gauge->m_maxMinorTick = 1000000;\n    m_levelerGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_levelerGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_levelerGauge->m_gauge->m_needleWidth = 4.0f;\n    m_levelerGauge->m_gauge->m_gamma = 1.0f;\n    m_levelerGauge->m_gauge->m_needleKs = 1000.0f;\n    m_levelerGauge->m_gauge->m_needleKd = 20.0f;\n    m_levelerGauge->m_gauge->setBandCount(0);\n\n    m_convolutionGauge = addElement<LabeledGauge>();\n    m_convolutionGauge->m_title = \"Conv.\";\n    m_convolutionGauge->m_unit = \"\";\n    m_convolutionGauge->m_precision = 1;\n    m_convolutionGauge->setLocalPosition({ 0, 0 });\n    m_convolutionGauge->m_gauge->m_min = 0;\n    m_convolutionGauge->m_gauge->m_max = 100;\n    m_convolutionGauge->m_gauge->m_minorStep = 5;\n    m_convolutionGauge->m_gauge->m_majorStep = 10;\n    m_convolutionGauge->m_gauge->m_maxMinorTick = 1000000;\n    m_convolutionGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_convolutionGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_convolutionGauge->m_gauge->m_needleWidth = 4.0f;\n    m_convolutionGauge->m_gauge->m_gamma = 1.0f;\n    m_convolutionGauge->m_gauge->m_needleKs = 1000.0f;\n    m_convolutionGauge->m_gauge->m_needleKd = 20.0f;\n    m_convolutionGauge->m_gauge->setBandCount(0);\n\n    m_highFreqFilterGauge = addElement<LabeledGauge>();\n    m_highFreqFilterGauge->m_title = \"+HF\";\n    m_highFreqFilterGauge->m_unit = \"\";\n    m_highFreqFilterGauge->m_precision = 2;\n    m_highFreqFilterGauge->setLocalPosition({ 0, 0 });\n    m_highFreqFilterGauge->m_gauge->m_min = 0;\n    m_highFreqFilterGauge->m_gauge->m_max = 10;\n    m_highFreqFilterGauge->m_gauge->m_minorStep = 1;\n    m_highFreqFilterGauge->m_gauge->m_majorStep = 2;\n    m_highFreqFilterGauge->m_gauge->m_maxMinorTick = 10;\n    m_highFreqFilterGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_highFreqFilterGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_highFreqFilterGauge->m_gauge->m_needleWidth = 4.0f;\n    m_highFreqFilterGauge->m_gauge->m_gamma = 1.0f;\n    m_highFreqFilterGauge->m_gauge->m_needleKs = 1000.0f;\n    m_highFreqFilterGauge->m_gauge->m_needleKd = 20.0f;\n    m_highFreqFilterGauge->m_gauge->setBandCount(0);\n\n    m_noise0Gauge = addElement<LabeledGauge>();\n    m_noise0Gauge->m_title = \"~ LF\";\n    m_noise0Gauge->m_unit = \"\";\n    m_noise0Gauge->m_precision = 1;\n    m_noise0Gauge->setLocalPosition({ 0, 0 });\n    m_noise0Gauge->m_gauge->m_min = 0;\n    m_noise0Gauge->m_gauge->m_max = 100;\n    m_noise0Gauge->m_gauge->m_minorStep = 5;\n    m_noise0Gauge->m_gauge->m_majorStep = 10;\n    m_noise0Gauge->m_gauge->m_maxMinorTick = 1000000;\n    m_noise0Gauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_noise0Gauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_noise0Gauge->m_gauge->m_needleWidth = 4.0f;\n    m_noise0Gauge->m_gauge->m_gamma = 1.0f;\n    m_noise0Gauge->m_gauge->m_needleKs = 1000.0f;\n    m_noise0Gauge->m_gauge->m_needleKd = 20.0f;\n    m_noise0Gauge->m_gauge->setBandCount(0);\n\n    m_noise1Gauge = addElement<LabeledGauge>();\n    m_noise1Gauge->m_title = \"~ HF\";\n    m_noise1Gauge->m_unit = \"\";\n    m_noise1Gauge->m_precision = 1;\n    m_noise1Gauge->setLocalPosition({ 0, 0 });\n    m_noise1Gauge->m_gauge->m_min = 0;\n    m_noise1Gauge->m_gauge->m_max = 100;\n    m_noise1Gauge->m_gauge->m_minorStep = 5;\n    m_noise1Gauge->m_gauge->m_majorStep = 10;\n    m_noise1Gauge->m_gauge->m_maxMinorTick = 1000000;\n    m_noise1Gauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_noise1Gauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_noise1Gauge->m_gauge->m_needleWidth = 4.0f;\n    m_noise1Gauge->m_gauge->m_gamma = 1.0f;\n    m_noise1Gauge->m_gauge->m_needleKs = 1000.0f;\n    m_noise1Gauge->m_gauge->m_needleKd = 20.0f;\n    m_noise1Gauge->m_gauge->setBandCount(0);\n}\n\nvoid MixerCluster::destroy() {\n    UiElement::destroy();\n}\n\nvoid MixerCluster::update(float dt) {\n    UiElement::update(dt);\n}\n\nvoid MixerCluster::render() {\n    Grid grid;\n    grid.h_cells = 6;\n    grid.v_cells = 1;\n\n    Synthesizer::AudioParameters parameters = m_simulator->synthesizer().getAudioParameters();\n\n    m_volumeGauge->m_bounds = grid.get(m_bounds, 0, 0);\n    m_volumeGauge->m_gauge->m_value = (float)parameters.volume * 100.0f;\n\n    m_convolutionGauge->m_bounds = grid.get(m_bounds, 1, 0);\n    m_convolutionGauge->m_gauge->m_value = (float)parameters.convolution * 100.0f;\n\n    m_highFreqFilterGauge->m_bounds = grid.get(m_bounds, 2, 0);\n    m_highFreqFilterGauge->m_gauge->m_value = (float)parameters.dF_F_mix * 1000.0f;\n\n    m_noise0Gauge->m_bounds = grid.get(m_bounds, 3, 0);\n    m_noise0Gauge->m_gauge->m_value = (float)parameters.airNoise * 100.0f;\n\n    m_noise1Gauge->m_bounds = grid.get(m_bounds, 4, 0);\n    m_noise1Gauge->m_gauge->m_value = (float)parameters.inputSampleNoise * 100.0f;\n\n    const double gain = m_simulator->synthesizer().getLevelerGain();\n    m_levelerGauge->m_bounds = grid.get(m_bounds, 5, 0);\n    m_levelerGauge->m_gauge->m_value =\n        100.0f * (float)((gain - parameters.levelerMinGain) / parameters.levelerMaxGain);\n\n    UiElement::render();\n}\n"
  },
  {
    "path": "src/oscilloscope.cpp",
    "content": "#include \"../include/oscilloscope.h\"\n\n#include \"../include/geometry_generator.h\"\n#include \"../include/engine_sim_application.h\"\n#include \"../include/ui_utilities.h\"\n\nOscilloscope::Oscilloscope() {\n    m_xMin = m_xMax = 0;\n    m_yMin = m_yMax = 0;\n    m_lineWidth = 1;\n\n    m_points = nullptr;\n    m_renderBuffer = nullptr;\n    m_writeIndex = 0;\n    m_bufferSize = 0;\n    m_pointCount = 0;\n    m_drawReverse = true;\n    m_checkMouse = true;\n    m_drawZero = true;\n    m_dynamicallyResizeX = false;\n    m_dynamicallyResizeY = true;\n\n    i_color = ysMath::Constants::One;\n}\n\nOscilloscope::~Oscilloscope() {\n    assert(m_points == nullptr);\n    assert(m_renderBuffer == nullptr);\n}\n\nvoid Oscilloscope::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n\n    i_color = m_app->getRed();\n}\n\nvoid Oscilloscope::destroy() {\n    delete[] m_points;\n    delete[] m_renderBuffer;\n\n    m_points = nullptr;\n    m_renderBuffer = nullptr;\n\n    m_writeIndex = 0;\n    m_bufferSize = 0;\n    m_pointCount = 0;\n}\n\nvoid Oscilloscope::update(float dt) {\n    UiElement::update(dt);\n\n    m_mouseBounds = m_bounds;\n}\n\nvoid Oscilloscope::render() {\n    render(m_bounds);\n}\n\nvoid Oscilloscope::render(const Bounds &bounds) {\n    for (int i = 0; i < m_pointCount; ++i) {\n        const int index = (m_writeIndex - m_pointCount + i + m_bufferSize) % m_bufferSize;\n        m_renderBuffer[index] = dataPointToRenderPosition(m_points[index], bounds);\n    }\n\n    const int start = (m_writeIndex - m_pointCount + m_bufferSize) % m_bufferSize;\n    const int n0 = (start + m_pointCount) > m_bufferSize\n        ? m_bufferSize - start\n        : m_pointCount;\n    const int n1 = m_pointCount - n0;\n\n    GeometryGenerator::GeometryIndices lines;\n    GeometryGenerator::PathParameters params;\n    params.p0 = m_renderBuffer + start;\n    params.p1 = m_renderBuffer;\n    params.n0 = n0;\n    params.n1 = n1;\n\n    m_app->getGeometryGenerator()->startShape();\n\n    params.i = 0;\n    params.width = pixelsToUnits(0.5f) * (float)m_lineWidth;\n    if (!m_app->getGeometryGenerator()->startPath(params)) {\n        return;\n    }\n\n    Point prev = params.p0[0];\n    bool lastDetached = false;\n    for (int i = 1; i < n0 + n1; ++i) {\n        Point *p = (i >= n0)\n            ? params.p1\n            : params.p0;\n        const int index = (i >= n0)\n            ? i - n0\n            : i;\n        const float s = (float)(i) / (n0 + n1);\n        const Point p_i = p[index];\n        params.i = i;\n        params.width = (float)m_lineWidth * std::fmaxf(\n            pixelsToUnits(1.0f) * s,\n            pixelsToUnits(0.5f));\n\n        if (s > 0.95f) {\n            params.width += pixelsToUnits(((s - 0.95f) / 0.05f) * 2);\n        }\n\n        const bool detached =\n            prev.x > p_i.x\n            || std::abs(p_i.x - prev.x) > pixelsToUnits(100.0f);\n        m_app->getGeometryGenerator()->generatePathSegment(\n            params,\n            (detached || lastDetached) && !m_drawReverse);\n\n        lastDetached = detached;\n\n        prev = p_i;\n    }\n\n    m_app->getGeometryGenerator()->endShape(&lines);\n\n    resetShader();\n\n    if (m_drawZero) {\n        GeometryGenerator::GeometryIndices zeroLine;\n        const Point zeroA = dataPointToRenderPosition({ (float)m_xMin, 0.0f }, bounds);\n        const Point zeroB = dataPointToRenderPosition({ (float)m_xMax, 0.0f }, bounds);\n\n        GeometryGenerator::Line2dParameters params;\n        params.x0 = zeroA.x;\n        params.x1 = zeroB.x;\n        params.y0 = zeroA.y;\n        params.y1 = zeroB.y;\n        params.lineWidth = pixelsToUnits(0.5f);\n\n        m_app->getGeometryGenerator()->startShape();\n        m_app->getGeometryGenerator()->generateLine2d(params);\n        m_app->getGeometryGenerator()->endShape(&zeroLine);\n\n        m_app->getShaders()->SetBaseColor(mix(m_app->getForegroundColor(), m_app->getBackgroundColor(), 0.95f));\n        m_app->drawGenerated(zeroLine, 0x11, m_app->getShaders()->GetUiFlags());\n    }\n\n    m_app->getShaders()->SetBaseColor(i_color);\n    m_app->drawGenerated(lines, 0x11, m_app->getShaders()->GetUiFlags());\n}\n\nPoint Oscilloscope::dataPointToRenderPosition(\n    const DataPoint &p,\n    const Bounds &bounds) const\n{\n    const float width = bounds.width();\n    const float height = bounds.height();\n\n    const float s_x = (float)((p.x - m_xMin) / (m_xMax - m_xMin));\n    const float s_y = (float)((p.y - m_yMin) / (m_yMax - m_yMin));\n\n    const Point local = { s_x * width, s_y * height };\n\n    return getRenderPoint(bounds.getPosition(Bounds::bl) + local);\n}\n\nvoid Oscilloscope::addDataPoint(double x, double y) {\n    m_points[m_writeIndex] = { x, y };\n    m_writeIndex = (m_writeIndex + 1) % m_bufferSize;\n    m_pointCount = (m_pointCount >= m_bufferSize)\n        ? m_bufferSize\n        : m_pointCount + 1;\n\n    if (m_dynamicallyResizeY) {\n        if (y + std::abs(0.1 * y) >= m_yMax) {\n            m_yMax = y + std::abs(0.1 * y);\n        }\n        else if (y - std::abs(0.1 * y) <= m_yMin) {\n            m_yMin = y - std::abs(0.1 * y);\n        }\n    }\n    if (m_dynamicallyResizeX) {\n        if (x + std::abs(0.1 * x) >= m_xMax) {\n            m_xMax = x + std::abs(0.1 * x);\n        }\n        else if (x - std::abs(0.1 * x) <= m_xMin) {\n            m_xMin = x - std::abs(0.1 * x);\n        }\n    }\n}\n\nvoid Oscilloscope::setBufferSize(int n) {\n    m_points = new DataPoint[n];\n    m_renderBuffer = new Point[n];\n    m_bufferSize = n;\n\n    reset();\n}\n\nvoid Oscilloscope::reset() {\n    m_writeIndex = 0;\n    m_pointCount = 0;\n}\n"
  },
  {
    "path": "src/oscilloscope_cluster.cpp",
    "content": "#include \"../include/oscilloscope_cluster.h\"\n\n#include \"../include/engine_sim_application.h\"\n\n#include <sstream>\n\nOscilloscopeCluster::OscilloscopeCluster() {\n    m_simulator = nullptr;\n    m_torqueScope = nullptr;\n    m_powerScope = nullptr;\n    m_totalExhaustFlowScope = nullptr;\n    m_intakeFlowScope = nullptr;\n    m_exhaustFlowScope = nullptr;\n    m_exhaustValveLiftScope = nullptr;\n    m_intakeValveLiftScope = nullptr;\n    m_audioWaveformScope = nullptr;\n    m_cylinderPressureScope = nullptr;\n    m_sparkAdvanceScope = nullptr;\n    m_cylinderMoleculesScope = nullptr;\n    m_pvScope = nullptr;\n\n    for (int i = 0; i < MaxLayeredScopes; ++i) {\n        m_currentFocusScopes[i] = nullptr;\n    }\n\n    m_torque = 0;\n    m_power = 0;\n\n    m_updatePeriod = 0.25f;\n    m_updateTimer = 0.0f;\n}\n\nOscilloscopeCluster::~OscilloscopeCluster() {\n    /* void */\n}\n\nvoid OscilloscopeCluster::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n\n    m_torqueScope = addElement<Oscilloscope>(this);\n    m_powerScope = addElement<Oscilloscope>(this);\n    m_exhaustFlowScope = addElement<Oscilloscope>(this);\n    m_totalExhaustFlowScope = addElement<Oscilloscope>(this);\n    m_intakeFlowScope = addElement<Oscilloscope>(this);\n    m_audioWaveformScope = addElement<Oscilloscope>(this);\n    m_intakeValveLiftScope = addElement<Oscilloscope>(this);\n    m_exhaustValveLiftScope = addElement<Oscilloscope>(this);\n    m_cylinderPressureScope = addElement<Oscilloscope>(this);\n    m_sparkAdvanceScope = addElement<Oscilloscope>(this);\n    m_cylinderMoleculesScope = addElement<Oscilloscope>(this);\n    m_pvScope = addElement<Oscilloscope>(this);\n\n    // Torque\n    m_torqueScope->setBufferSize(100);\n    m_torqueScope->m_xMin = 0.0f;\n    m_torqueScope->m_yMin = 0.0f;\n    m_torqueScope->m_yMax = 0.0f;\n    m_torqueScope->m_lineWidth = 2.0f;\n    m_torqueScope->m_drawReverse = false;\n    m_torqueScope->m_dynamicallyResizeX = true;\n    m_torqueScope->i_color = m_app->getOrange();\n\n    // Power\n    m_powerScope->setBufferSize(100);\n    m_powerScope->m_xMin = 0.0f;\n    m_powerScope->m_yMin = 0.0f;\n    m_powerScope->m_yMax = 0.0f;\n    m_powerScope->m_lineWidth = 2.0f;\n    m_powerScope->m_drawReverse = false;\n    m_powerScope->m_dynamicallyResizeX = true;\n    m_powerScope->i_color = m_app->getPink();\n\n    // Total exhaust flow\n    m_totalExhaustFlowScope->setBufferSize(1024);\n    m_totalExhaustFlowScope->m_xMin = 0.0f;\n    m_totalExhaustFlowScope->m_xMax = constants::pi * 4;\n    m_totalExhaustFlowScope->m_yMin = -units::flow(10, units::scfm);\n    m_totalExhaustFlowScope->m_yMax = units::flow(10, units::scfm);\n    m_totalExhaustFlowScope->m_lineWidth = 2.0f;\n    m_totalExhaustFlowScope->m_drawReverse = false;\n    m_totalExhaustFlowScope->i_color = m_app->getOrange();\n\n    // Exhaust flow\n    m_exhaustFlowScope->setBufferSize(1024);\n    m_exhaustFlowScope->m_xMin = 0.0f;\n    m_exhaustFlowScope->m_xMax = constants::pi * 4;\n    m_exhaustFlowScope->m_yMin = -units::flow(10.0, units::scfm);\n    m_exhaustFlowScope->m_yMax = units::flow(10.0, units::scfm);\n    m_exhaustFlowScope->m_lineWidth = 2.0f;\n    m_exhaustFlowScope->m_drawReverse = false;\n    m_exhaustFlowScope->i_color = m_app->getOrange();\n\n    // Intake flow\n    m_intakeFlowScope->setBufferSize(1024);\n    m_intakeFlowScope->m_xMin = 0.0f;\n    m_intakeFlowScope->m_xMax = constants::pi * 4;\n    m_intakeFlowScope->m_yMin = -units::flow(10.0, units::scfm);\n    m_intakeFlowScope->m_yMax = units::flow(10.0, units::scfm);\n    m_intakeFlowScope->m_lineWidth = 2.0f;\n    m_intakeFlowScope->m_drawReverse = false;\n    m_intakeFlowScope->i_color = m_app->getBlue();\n\n    // Cylinder molcules\n    m_cylinderMoleculesScope->setBufferSize(1024);\n    m_cylinderMoleculesScope->m_xMin = 0.0f;\n    m_cylinderMoleculesScope->m_xMax = constants::pi * 4;\n    m_cylinderMoleculesScope->m_yMin = -0.05;\n    m_cylinderMoleculesScope->m_yMax = 0.2;\n    m_cylinderMoleculesScope->m_lineWidth = 4.0f;\n    m_cylinderMoleculesScope->m_drawReverse = false;\n    m_cylinderMoleculesScope->i_color = m_app->getForegroundColor();\n\n    // Audio waveform scope\n    m_audioWaveformScope->setBufferSize(44100 / 50);\n    m_audioWaveformScope->m_xMin = 0.0f;\n    m_audioWaveformScope->m_xMax = 44100 / 10;\n    m_audioWaveformScope->m_yMin = -1.5f;\n    m_audioWaveformScope->m_yMax = 1.5f;\n    m_audioWaveformScope->m_lineWidth = 2.0f;\n    m_audioWaveformScope->m_drawReverse = false;\n    m_audioWaveformScope->i_color = m_app->getBlue();\n\n    // Valve lift scopes\n    m_exhaustValveLiftScope->setBufferSize(1024);\n    m_exhaustValveLiftScope->m_xMin = 0.0f;\n    m_exhaustValveLiftScope->m_xMax = constants::pi * 4;\n    m_exhaustValveLiftScope->m_yMin = (float)units::distance(-10, units::thou);\n    m_exhaustValveLiftScope->m_yMax = (float)units::distance(10, units::thou);\n    m_exhaustValveLiftScope->m_lineWidth = 2.0f;\n    m_exhaustValveLiftScope->m_drawReverse = false;\n    m_exhaustValveLiftScope->i_color = m_app->getOrange();\n\n    m_intakeValveLiftScope->setBufferSize(1024);\n    m_intakeValveLiftScope->m_xMin = 0.0f;\n    m_intakeValveLiftScope->m_xMax = constants::pi * 4;\n    m_intakeValveLiftScope->m_yMin = (float)units::distance(-10, units::thou);\n    m_intakeValveLiftScope->m_yMax = (float)units::distance(10, units::thou);\n    m_intakeValveLiftScope->m_lineWidth = 2.0f;\n    m_intakeValveLiftScope->m_drawReverse = false;\n    m_intakeValveLiftScope->i_color = m_app->getBlue();\n\n    // Cylinder pressure scope\n    m_cylinderPressureScope->setBufferSize(1024);\n    m_cylinderPressureScope->m_xMin = 0.0f;\n    m_cylinderPressureScope->m_xMax = constants::pi * 4;\n    m_cylinderPressureScope->m_yMin = -(float)std::sqrt(units::pressure(1, units::psi));\n    m_cylinderPressureScope->m_yMax = (float)std::sqrt(units::pressure(1, units::psi));\n    m_cylinderPressureScope->m_lineWidth = 2.0f;\n    m_cylinderPressureScope->m_drawReverse = false;\n    m_cylinderPressureScope->i_color = m_app->getOrange();\n\n    // Pressure volume scope\n    m_pvScope->setBufferSize(1024);\n    m_pvScope->m_xMin = 0.0f;\n    m_pvScope->m_xMax = units::volume(0.1, units::L);\n    m_pvScope->m_yMin = -(float)std::sqrt(units::pressure(1, units::psi));\n    m_pvScope->m_yMax = (float)std::sqrt(units::pressure(1, units::psi));\n    m_pvScope->m_lineWidth = 2.0f;\n    m_pvScope->m_drawReverse = true;\n    m_pvScope->i_color = m_app->getOrange();\n    m_pvScope->m_dynamicallyResizeX = true;\n\n    // Spark advance scope\n    m_sparkAdvanceScope->setBufferSize(1024);\n    m_sparkAdvanceScope->m_xMin = 0.0f;\n    m_sparkAdvanceScope->m_xMax = units::rpm(10000);\n    m_sparkAdvanceScope->m_yMin = -units::angle(30, units::deg);\n    m_sparkAdvanceScope->m_yMax = units::angle(60, units::deg);\n    m_sparkAdvanceScope->m_lineWidth = 2.0f;\n    m_sparkAdvanceScope->m_drawReverse = true;\n    m_sparkAdvanceScope->i_color = m_app->getOrange();\n\n    m_currentFocusScopes[0] = m_totalExhaustFlowScope;\n    m_currentFocusScopes[1] = nullptr;\n\n    m_torqueUnits = app->getAppSettings()->torqueUnits;\n    m_powerUnits = app->getAppSettings()->powerUnits;\n}\n\nvoid OscilloscopeCluster::destroy() {\n    UiElement::destroy();\n}\n\nvoid OscilloscopeCluster::signal(UiElement *element, Event event) {\n    if (event == Event::Clicked) {\n        if (element == m_audioWaveformScope) {\n            m_currentFocusScopes[0] = m_audioWaveformScope;\n            m_currentFocusScopes[1] = nullptr;\n        }\n        else if (element == m_powerScope || element == m_torqueScope) {\n            m_currentFocusScopes[0] = m_torqueScope;\n            m_currentFocusScopes[1] = m_powerScope;\n            m_currentFocusScopes[2] = nullptr;\n        }\n        else if (element == m_totalExhaustFlowScope) {\n            m_currentFocusScopes[0] = m_totalExhaustFlowScope;\n            m_currentFocusScopes[1] = nullptr;\n        }\n        else if (\n            element == m_intakeValveLiftScope\n            || element == m_exhaustValveLiftScope)\n        {\n            m_currentFocusScopes[0] = m_intakeValveLiftScope;\n            m_currentFocusScopes[1] = m_exhaustValveLiftScope;\n            m_currentFocusScopes[2] = nullptr;\n        }\n        else if (element == m_pvScope) {\n            m_currentFocusScopes[0] = m_pvScope;\n            m_currentFocusScopes[1] = nullptr;\n        }\n        else if (\n            element == m_intakeFlowScope\n            || element == m_exhaustFlowScope\n            || element == m_cylinderMoleculesScope)\n        {\n            m_currentFocusScopes[0] = m_intakeFlowScope;\n            m_currentFocusScopes[1] = m_exhaustFlowScope;\n            m_currentFocusScopes[2] = m_cylinderMoleculesScope;\n            m_currentFocusScopes[3] = nullptr;\n        }\n        else if (element == m_cylinderPressureScope) {\n            m_currentFocusScopes[0] = m_cylinderPressureScope;\n            m_currentFocusScopes[1] = nullptr;\n        }\n        else if (element == m_sparkAdvanceScope) {\n            m_currentFocusScopes[0] = m_sparkAdvanceScope;\n            m_currentFocusScopes[1] = nullptr;\n        }\n    }\n}\n\nvoid OscilloscopeCluster::update(float dt) {\n    const double torque = (m_torqueUnits == \"Nm\")\n        ? (units::convert(m_simulator->getFilteredDynoTorque(), units::Nm))\n        : (units::convert(m_simulator->getFilteredDynoTorque(), units::ft_lb));\n\n    const double power = (m_powerUnits == \"kW\")\n        ? (units::convert(m_simulator->getDynoPower(), units::kW))\n        : (units::convert(m_simulator->getDynoPower(), units::hp));\n\n    m_torque = m_torque * 0.95 + 0.05 * torque;\n    m_power = m_power * 0.95 + 0.05 * power;\n\n    Engine *engine = m_simulator->getEngine();\n    if (engine != nullptr) {\n        if (m_updateTimer <= 0 && m_simulator->m_dyno.m_enabled) {\n            m_updateTimer = m_updatePeriod;\n\n            m_torqueScope->addDataPoint(engine->getRpm(), m_torque);\n            m_powerScope->addDataPoint(engine->getRpm(), m_power);\n        }\n\n        m_sparkAdvanceScope->addDataPoint(\n            -engine->getCrankshaft(0)->m_body.v_theta,\n            engine->getIgnitionModule()->getTimingAdvance());\n    }\n\n    m_updateTimer -= dt;\n\n    UiElement::update(dt);\n}\n\nvoid OscilloscopeCluster::render() {\n    Grid grid;\n    grid.h_cells = 3;\n    grid.v_cells = 4;\n\n    const Bounds &hpTorqueBounds = grid.get(m_bounds, 0, 3);\n    renderScope(m_torqueScope, hpTorqueBounds, \"Torque/Power\");\n    renderScope(m_powerScope, hpTorqueBounds, \"\", true);\n\n    const Bounds &valveLiftBounds = grid.get(m_bounds, 2, 2);\n    renderScope(m_intakeValveLiftScope, valveLiftBounds, \"Valve Lift\");\n    renderScope(m_exhaustValveLiftScope, valveLiftBounds, \"\", true);\n\n    const Bounds &flowBounds = grid.get(m_bounds, 2, 3);\n    renderScope(m_intakeFlowScope, flowBounds, \"Flow\");\n    renderScope(m_exhaustFlowScope, flowBounds, \"\", true);\n    renderScope(m_cylinderMoleculesScope, flowBounds, \"\", true);\n\n    const Bounds &audioWaveformBounds = grid.get(m_bounds, 0, 2);\n    renderScope(m_audioWaveformScope, audioWaveformBounds, \"Waveform\");\n\n    const Bounds &cylinderPressureBounds = grid.get(m_bounds, 1, 3);\n    renderScope(m_pvScope, cylinderPressureBounds, \"pressure-volume\");\n\n    const Bounds &totalExhaustPressureBounds = grid.get(m_bounds, 1, 2);\n    renderScope(m_totalExhaustFlowScope, totalExhaustPressureBounds, \"Total Exhaust Flow\");\n\n    const Bounds &focusBounds = grid.get(m_bounds, 0, 0, 3, 2);\n    Bounds focusTitle = focusBounds;\n    focusTitle.m0.y = focusTitle.m1.y - (24.0f + 15.0f);\n    Bounds focusBody = focusBounds;\n    focusBody.m1 = focusBody.m1 - Point(0.0f, 24.0f + 15.0f);\n\n    drawFrame(focusTitle, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor());\n    drawFrame(focusBody, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor());\n\n    for (int i = 0; i < MaxLayeredScopes; ++i) {\n        if (m_currentFocusScopes[i] != nullptr) {\n            m_currentFocusScopes[i]->render(focusBody);\n        }\n        else break;\n    }\n\n    UiElement::render();\n}\n\nvoid OscilloscopeCluster::sample() {\n    Engine *engine = m_simulator->getEngine();\n    if (engine == nullptr) return;\n\n    const double cylinderPressure = engine->getChamber(0)->m_system.pressure()\n        + engine->getChamber(0)->m_system.dynamicPressure(-1.0, 0.0);\n\n    if (m_simulator->getCurrentIteration() % 2 == 0) {\n        double cycleAngle = engine->getCrankshaft(0)->getCycleAngle();\n        if (!engine->isSpinningCw()) {\n            cycleAngle = 4 * constants::pi - cycleAngle;\n        }\n\n        getTotalExhaustFlowOscilloscope()->addDataPoint(\n            cycleAngle,\n            m_simulator->getTotalExhaustFlow() / m_simulator->getTimestep());\n        getCylinderPressureScope()->addDataPoint(\n            engine->getCrankshaft(0)->getCycleAngle(constants::pi),\n            std::sqrt(cylinderPressure));\n        getExhaustFlowOscilloscope()->addDataPoint(\n            cycleAngle,\n            engine->getChamber(0)->getLastTimestepExhaustFlow() / m_simulator->getTimestep());\n        getIntakeFlowOscilloscope()->addDataPoint(\n            cycleAngle,\n            engine->getChamber(0)->getLastTimestepIntakeFlow() / m_simulator->getTimestep());\n        getCylinderMoleculesScope()->addDataPoint(\n            cycleAngle,\n            engine->getChamber(0)->m_system.n());\n        getExhaustValveLiftOscilloscope()->addDataPoint(\n            cycleAngle,\n            engine->getChamber(0)->getCylinderHead()->exhaustValveLift(\n                engine->getChamber(0)->getPiston()->getCylinderIndex()));\n        getIntakeValveLiftOscilloscope()->addDataPoint(\n            cycleAngle,\n            engine->getChamber(0)->getCylinderHead()->intakeValveLift(\n                engine->getChamber(0)->getPiston()->getCylinderIndex()));\n        getPvScope()->addDataPoint(\n            engine->getChamber(0)->getVolume(),\n            std::sqrt(engine->getChamber(0)->m_system.pressure()));\n    }\n\n    m_exhaustFlowScope->m_yMin = m_intakeFlowScope->m_yMin =\n        std::fmin(m_intakeFlowScope->m_yMin, m_exhaustFlowScope->m_yMin);\n    m_exhaustFlowScope->m_yMax = m_intakeFlowScope->m_yMax =\n        std::fmax(m_intakeFlowScope->m_yMax, m_exhaustFlowScope->m_yMax);\n\n    m_torqueScope->m_yMin = m_powerScope->m_yMin =\n        std::fmin(m_torqueScope->m_yMin, m_powerScope->m_yMin);\n    m_torqueScope->m_yMax = m_powerScope->m_yMax =\n        std::fmax(m_torqueScope->m_yMax, m_powerScope->m_yMax);\n\n    m_powerScope->m_xMax = m_torqueScope->m_xMax =\n        std::fmax(m_powerScope->m_xMax, units::toRpm(engine->getSpeed()));\n}\n\nvoid OscilloscopeCluster::setSimulator(Simulator *simulator) {\n    m_simulator = simulator;\n}\n\nvoid OscilloscopeCluster::renderScope(\n    Oscilloscope *osc,\n    const Bounds &bounds,\n    const std::string &title,\n    bool overlay)\n{\n    if (!overlay) {\n        drawFrame(bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor());\n    }\n\n    if (osc == m_currentFocusScopes[0]) {\n        Grid grid;\n        grid.h_cells = 3;\n        grid.v_cells = 4;\n\n        const Bounds &focusBounds = grid.get(m_bounds, 0, 0, 3, 2);\n        Bounds focusTitle = focusBounds;\n        focusTitle.m1 -= Point(0.0f, 24.0f + 15.0f);\n        Bounds focusBody = focusBounds;\n        focusTitle.m1 += Point(0.0f, 24.0f + 15.0f);\n\n        drawText(title, focusTitle.inset(20.0f), 24.0f, Bounds::tl);\n    }\n\n    osc->m_bounds = bounds;\n}\n"
  },
  {
    "path": "src/part.cpp",
    "content": "#include \"../include/part.h\"\n\nPart::Part() {\n    /* void */\n}\n\nPart::~Part() {\n    /* void */\n}\n\nvoid Part::destroy() {\n    /* void */\n}\n"
  },
  {
    "path": "src/performance_cluster.cpp",
    "content": "#include \"../include/performance_cluster.h\"\n\n#include \"../include/units.h\"\n#include \"../include/gauge.h\"\n#include \"../include/constants.h\"\n#include \"../include/engine_sim_application.h\"\n\n#include <sstream>\n\nPerformanceCluster::PerformanceCluster() {\n    m_simulator = nullptr;\n    m_timePerTimestepGauge = nullptr;\n    m_timePerTimestepGauge = nullptr;\n    m_fpsGauge = nullptr;\n    m_simSpeedGauge = nullptr;\n    m_simulationFrequencyGauge = nullptr;\n    m_inputSamplesGauge = nullptr;\n    m_audioLagGauge = nullptr;\n\n    m_timePerTimestep = 0.0;\n    m_filteredSimulationFrequency = 0.0;\n    m_inputBufferUsage = 0.0;\n    m_audioLatency = 0.0;\n}\n\nPerformanceCluster::~PerformanceCluster() {\n    /* void */\n}\n\nvoid PerformanceCluster::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n\n    constexpr float shortenAngle = (float)units::angle(1.0, units::deg);\n\n    m_timePerTimestepGauge = addElement<LabeledGauge>();\n    m_timePerTimestepGauge->m_title = \"RT/dT\";\n    m_timePerTimestepGauge->m_unit = \"\";\n    m_timePerTimestepGauge->m_precision = 1;\n    m_timePerTimestepGauge->setLocalPosition({ 0, 0 });\n    m_timePerTimestepGauge->m_gauge->m_min = 0;\n    m_timePerTimestepGauge->m_gauge->m_max = 200;\n    m_timePerTimestepGauge->m_gauge->m_minorStep = 5;\n    m_timePerTimestepGauge->m_gauge->m_majorStep = 10;\n    m_timePerTimestepGauge->m_gauge->m_maxMinorTick = 1000000;\n    m_timePerTimestepGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_timePerTimestepGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_timePerTimestepGauge->m_gauge->m_needleWidth = 4.0f;\n    m_timePerTimestepGauge->m_gauge->m_gamma = 1.0f;\n    m_timePerTimestepGauge->m_gauge->m_needleKs = 1000.0f;\n    m_timePerTimestepGauge->m_gauge->m_needleKd = 20.0f;\n    m_timePerTimestepGauge->m_gauge->setBandCount(3);\n    m_timePerTimestepGauge->m_gauge->setBand(\n        { m_app->getOrange(), 50.0f, 100.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0);\n    m_timePerTimestepGauge->m_gauge->setBand(\n        { m_app->getRed(), 100.0f, 200.0f, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 1);\n    m_timePerTimestepGauge->m_gauge->setBand(\n        { m_app->getBlue(), 0.0f, 50.0f, 3.0f, 6.0f, -shortenAngle, shortenAngle }, 2);\n\n    m_fpsGauge = addElement<LabeledGauge>();\n    m_fpsGauge->m_title = \"FPS\";\n    m_fpsGauge->m_unit = \"\";\n    m_fpsGauge->m_precision = 1;\n    m_fpsGauge->setLocalPosition({ 0, 0 });\n    m_fpsGauge->m_gauge->m_min = 0;\n    m_fpsGauge->m_gauge->m_max = 120;\n    m_fpsGauge->m_gauge->m_minorStep = 1;\n    m_fpsGauge->m_gauge->m_majorStep = 15;\n    m_fpsGauge->m_gauge->m_maxMinorTick = 60;\n    m_fpsGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_fpsGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_fpsGauge->m_gauge->m_needleWidth = 4.0f;\n    m_fpsGauge->m_gauge->m_gamma = 0.6f;\n    m_fpsGauge->m_gauge->m_needleKs = 1000.0f;\n    m_fpsGauge->m_gauge->m_needleKd = 20.0f;\n    m_fpsGauge->m_gauge->setBandCount(5);\n    m_fpsGauge->m_gauge->setBand(\n        { m_app->getGreen(), 58, 62, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0);\n    m_fpsGauge->m_gauge->setBand(\n        { m_app->getBlue(), 62, 120, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 1);\n    m_fpsGauge->m_gauge->setBand(\n        { m_app->getOrange(), 30, 58, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2);\n    m_fpsGauge->m_gauge->setBand(\n        { m_app->getRed(), 0, 30, 3.0f, 6.0f, -shortenAngle, shortenAngle }, 3);\n    m_fpsGauge->m_gauge->setBand(\n        { m_app->getForegroundColor(), 60, 120, 3.0f, 0.0f, -shortenAngle, shortenAngle }, 4);\n\n    m_simSpeedGauge = addElement<LabeledGauge>();\n    m_simSpeedGauge->m_title = \"1 / SPEED\";\n    m_simSpeedGauge->m_unit = \"\";\n    m_simSpeedGauge->m_spaceBeforeUnit = false;\n    m_simSpeedGauge->m_precision = 1;\n    m_simSpeedGauge->setLocalPosition({ 0, 0 });\n    m_simSpeedGauge->m_gauge->m_min = 0;\n    m_simSpeedGauge->m_gauge->m_max = 1000;\n    m_simSpeedGauge->m_gauge->m_minorStep = 50;\n    m_simSpeedGauge->m_gauge->m_majorStep = 100;\n    m_simSpeedGauge->m_gauge->m_maxMinorTick = 1000;\n    m_simSpeedGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_simSpeedGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_simSpeedGauge->m_gauge->m_needleWidth = 4.0f;\n    m_simSpeedGauge->m_gauge->m_gamma = 1.0f;\n    m_simSpeedGauge->m_gauge->m_needleKs = 1000.0f;\n    m_simSpeedGauge->m_gauge->m_needleKd = 20.0f;\n    m_simSpeedGauge->m_gauge->setBandCount(0);\n\n    m_audioLagGauge = addElement<LabeledGauge>();\n    m_audioLagGauge->m_title = \"LATENCY\";\n    m_audioLagGauge->m_unit = \"\";\n    m_audioLagGauge->m_spaceBeforeUnit = false;\n    m_audioLagGauge->m_precision = 1;\n    m_audioLagGauge->setLocalPosition({ 0, 0 });\n    m_audioLagGauge->m_gauge->m_min = 50;\n    m_audioLagGauge->m_gauge->m_max = 150;\n    m_audioLagGauge->m_gauge->m_minorStep = 5;\n    m_audioLagGauge->m_gauge->m_majorStep = 10;\n    m_audioLagGauge->m_gauge->m_maxMinorTick = 1000;\n    m_audioLagGauge->m_gauge->m_thetaMin = (float)constants::pi * 0.8f;\n    m_audioLagGauge->m_gauge->m_thetaMax = (float)constants::pi * 0.2f;\n    m_audioLagGauge->m_gauge->m_needleWidth = 4.0f;\n    m_audioLagGauge->m_gauge->m_gamma = 1.0f;\n    m_audioLagGauge->m_gauge->m_needleKs = 1000.0f;\n    m_audioLagGauge->m_gauge->m_needleKd = 20.0f;\n    m_audioLagGauge->m_gauge->setBandCount(3);\n    m_audioLagGauge->m_gauge->setBand(\n        { m_app->getForegroundColor(), 90, 110, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0);\n    m_audioLagGauge->m_gauge->setBand(\n        { m_app->getRed(), 50, 90, 3.0f, 6.0f, -shortenAngle, shortenAngle }, 1);\n    m_audioLagGauge->m_gauge->setBand(\n        { m_app->getOrange(), 110, 150, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 2);\n\n    m_inputSamplesGauge = addElement<LabeledGauge>();\n    m_inputSamplesGauge->m_title = \"IN. BUFFER\";\n    m_inputSamplesGauge->m_unit = \"\";\n    m_inputSamplesGauge->m_spaceBeforeUnit = false;\n    m_inputSamplesGauge->m_precision = 1;\n    m_inputSamplesGauge->setLocalPosition({ 0, 0 });\n    m_inputSamplesGauge->m_gauge->m_min = 0;\n    m_inputSamplesGauge->m_gauge->m_max = 200;\n    m_inputSamplesGauge->m_gauge->m_minorStep = 5;\n    m_inputSamplesGauge->m_gauge->m_majorStep = 10;\n    m_inputSamplesGauge->m_gauge->m_maxMinorTick = 1000;\n    m_inputSamplesGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_inputSamplesGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_inputSamplesGauge->m_gauge->m_needleWidth = 4.0f;\n    m_inputSamplesGauge->m_gauge->m_gamma = 1.0f;\n    m_inputSamplesGauge->m_gauge->m_needleKs = 1000.0f;\n    m_inputSamplesGauge->m_gauge->m_needleKd = 20.0f;\n    m_inputSamplesGauge->m_gauge->setBandCount(3);\n    m_inputSamplesGauge->m_gauge->setBand(\n        { m_app->getForegroundColor(), 90, 110, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0);\n    m_inputSamplesGauge->m_gauge->setBand(\n        { m_app->getRed(), 0, 10, 3.0f, 6.0f, -shortenAngle, shortenAngle }, 1);\n    m_inputSamplesGauge->m_gauge->setBand(\n        { m_app->getOrange(), 150, 200, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 2);\n\n    m_simulationFrequencyGauge = addElement<LabeledGauge>();\n    m_simulationFrequencyGauge->m_title = \"FREQUENCY\";\n    m_simulationFrequencyGauge->m_unit = \"hz\";\n    m_simulationFrequencyGauge->m_precision = 0;\n    m_simulationFrequencyGauge->setLocalPosition({ 0, 0 });\n    m_simulationFrequencyGauge->m_gauge->m_min = 1000;\n    m_simulationFrequencyGauge->m_gauge->m_max = 51000;\n    m_simulationFrequencyGauge->m_gauge->m_minorStep = 1000;\n    m_simulationFrequencyGauge->m_gauge->m_majorStep = 10000;\n    m_simulationFrequencyGauge->m_gauge->m_maxMinorTick = 50000;\n    m_simulationFrequencyGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_simulationFrequencyGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_simulationFrequencyGauge->m_gauge->m_needleWidth = 4.0f;\n    m_simulationFrequencyGauge->m_gauge->m_gamma = 0.9f;\n    m_simulationFrequencyGauge->m_gauge->m_needleKs = 1000.0f;\n    m_simulationFrequencyGauge->m_gauge->m_needleKd = 20.0f;\n    m_simulationFrequencyGauge->m_gauge->setBandCount(1);\n    m_simulationFrequencyGauge->m_gauge->setBand(\n        { m_app->getForegroundColor(), 11025, 44100, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0);\n}\n\nvoid PerformanceCluster::destroy() {\n    UiElement::destroy();\n}\n\nvoid PerformanceCluster::update(float dt) {\n    UiElement::update(dt);\n\n    m_filteredSimulationFrequency =\n        0.9 * m_filteredSimulationFrequency\n        + 0.1 * m_simulator->getSimulationFrequency() * m_simulator->getSimulationSpeed();\n}\n\nvoid PerformanceCluster::render() {\n    Grid grid;\n    grid.h_cells = 3;\n    grid.v_cells = 2;\n\n    constexpr float shortenAngle = (float)units::angle(1.0, units::deg);\n    const double idealTimePerTimestep = (1.0 / m_filteredSimulationFrequency);\n    m_timePerTimestepGauge->m_bounds = grid.get(m_bounds, 1, 0);\n    m_timePerTimestepGauge->m_gauge->m_value =\n        (float)(m_timePerTimestep / idealTimePerTimestep) * 100.0f;\n\n    m_fpsGauge->m_bounds = grid.get(m_bounds, 0, 0);\n    m_fpsGauge->m_gauge->m_value = m_app->getEngine()->GetAverageFramerate();\n\n    m_simSpeedGauge->m_bounds = grid.get(m_bounds, 2, 0);\n    m_simSpeedGauge->m_gauge->m_value = 1 / (float)m_simulator->getSimulationSpeed();\n\n    m_audioLagGauge->m_bounds = grid.get(m_bounds, 0, 1);\n    m_audioLagGauge->m_gauge->m_value = (float)m_audioLatency * 100.0f;\n\n    m_inputSamplesGauge->m_bounds = grid.get(m_bounds, 1, 1);\n    m_inputSamplesGauge->m_gauge->m_value = (float)m_inputBufferUsage * 100.0f;\n\n    m_simulationFrequencyGauge->m_bounds = grid.get(m_bounds, 2, 1);\n    m_simulationFrequencyGauge->m_gauge->m_value = (float)m_simulator->getSimulationFrequency();\n\n    UiElement::render();\n}\n\nvoid PerformanceCluster::addTimePerTimestepSample(double sample) {\n    const double r = 0.95;\n    m_timePerTimestep = r * m_timePerTimestep + (1 - r) * sample;\n}\n\nvoid PerformanceCluster::addAudioLatencySample(double sample) {\n    const double r = 0.95;\n    m_audioLatency = r * m_audioLatency + (1 - r) * sample;\n}\n\nvoid PerformanceCluster::addInputBufferUsageSample(double sample) {\n    const double r = 0.95;\n    m_inputBufferUsage = r * m_inputBufferUsage + (1 - r) * sample;\n}\n"
  },
  {
    "path": "src/piston.cpp",
    "content": "#include \"../include/piston.h\"\n\n#include \"../include/connecting_rod.h\"\n#include \"../include/crankshaft.h\"\n#include \"../include/cylinder_bank.h\"\n\n#include <cmath>\n\nPiston::Piston() {\n    m_rod = nullptr;\n    m_bank = nullptr;\n    m_cylinderConstraint = nullptr;\n    m_cylinderIndex = -1;\n    m_compressionHeight = 0.0;\n    m_displacement = 0.0;\n    m_wristPinLocation = 0.0;\n    m_mass = 0.0;\n    m_blowby_k = 0.0;\n}\n\nPiston::~Piston() {\n    /* void */\n}\n\nvoid Piston::initialize(const Parameters &params) {\n    m_rod = params.Rod;\n    m_bank = params.Bank;\n    m_cylinderIndex = params.CylinderIndex;\n    m_compressionHeight = params.CompressionHeight;\n    m_displacement = params.Displacement;\n    m_wristPinLocation = params.WristPinPosition;\n    m_mass = params.mass;\n    m_blowby_k = params.BlowbyFlowCoefficient;\n}\n\nvoid Piston::destroy() {\n    /* void */\n}\n\ndouble Piston::relativeX() const {\n    return m_body.p_x - m_bank->getX();\n}\n\ndouble Piston::relativeY() const {\n    return m_body.p_y - m_bank->getY();\n}\n\ndouble Piston::calculateCylinderWallForce() const {\n    return std::sqrt(\n        m_cylinderConstraint->F_x[0][0] * m_cylinderConstraint->F_x[0][0]\n        + m_cylinderConstraint->F_y[0][0] * m_cylinderConstraint->F_y[0][0]);\n}\n"
  },
  {
    "path": "src/piston_engine_simulator.cpp",
    "content": "#include \"../include/piston_engine_simulator.h\"\n\n#include \"../include/constants.h\"\n#include \"../include/units.h\"\n\n#include <cmath>\n#include <assert.h>\n#include <chrono>\n#include <set>\n\nPistonEngineSimulator::PistonEngineSimulator() {\n    m_engine = nullptr;\n    m_transmission = nullptr;\n    m_vehicle = nullptr;\n    m_delayFilters = nullptr;\n\n    m_crankConstraints = nullptr;\n    m_cylinderWallConstraints = nullptr;\n    m_linkConstraints = nullptr;\n    m_crankshaftFrictionConstraints = nullptr;\n    m_crankshaftLinks = nullptr;\n\n    m_exhaustFlowStagingBuffer = nullptr;\n\n    m_derivativeFilter.m_dt = 1.0;\n    m_fluidSimulationSteps = 8;\n}\n\nPistonEngineSimulator::~PistonEngineSimulator() {\n    assert(m_crankConstraints == nullptr);\n    assert(m_cylinderWallConstraints == nullptr);\n    assert(m_linkConstraints == nullptr);\n    assert(m_crankshaftFrictionConstraints == nullptr);\n    assert(m_exhaustFlowStagingBuffer == nullptr);\n    assert(m_delayFilters == nullptr);\n    assert(m_antialiasingFilters == nullptr);\n}\n\nvoid PistonEngineSimulator::loadSimulation(Engine *engine, Vehicle *vehicle, Transmission *transmission) {\n    Simulator::loadSimulation(engine, vehicle, transmission);\n\n    m_engine = engine;\n    m_vehicle = vehicle;\n    m_transmission = transmission;\n\n    const int crankCount = m_engine->getCrankshaftCount();\n    const int cylinderCount = m_engine->getCylinderCount();\n    const int linkCount = cylinderCount * 2;\n\n    if (crankCount <= 0) return;\n\n    m_crankConstraints = new atg_scs::FixedPositionConstraint[crankCount];\n    m_cylinderWallConstraints = new atg_scs::LineConstraint[cylinderCount];\n    m_linkConstraints = new atg_scs::LinkConstraint[linkCount];\n    m_crankshaftFrictionConstraints = new atg_scs::RotationFrictionConstraint[crankCount];\n    m_crankshaftLinks = new atg_scs::ClutchConstraint[crankCount - 1];\n    m_delayFilters = new DelayFilter[cylinderCount];\n\n    const double ks = 5000;\n    const double kd = 10;\n\n    for (int i = 0; i < crankCount; ++i) {\n        Crankshaft *outputShaft = m_engine->getCrankshaft(0);\n        Crankshaft *crankshaft = m_engine->getCrankshaft(i);\n\n        m_crankConstraints[i].setBody(&crankshaft->m_body);\n        m_crankConstraints[i].setWorldPosition(\n            crankshaft->getPosX(),\n            crankshaft->getPosY());\n        m_crankConstraints[i].setLocalPosition(0.0, 0.0);\n        m_crankConstraints[i].m_kd = kd;\n        m_crankConstraints[i].m_ks = ks;\n\n        crankshaft->m_body.p_x = crankshaft->getPosX();\n        crankshaft->m_body.p_y = crankshaft->getPosY();\n        crankshaft->m_body.theta = 0;\n        crankshaft->m_body.m =\n            crankshaft->getMass() + crankshaft->getFlywheelMass();\n        crankshaft->m_body.I = crankshaft->getMomentOfInertia();\n\n        m_crankshaftFrictionConstraints[i].m_minTorque = -crankshaft->getFrictionTorque();\n        m_crankshaftFrictionConstraints[i].m_maxTorque = crankshaft->getFrictionTorque();\n        m_crankshaftFrictionConstraints[i].setBody(&m_engine->getCrankshaft(i)->m_body);\n\n        m_system->addRigidBody(&m_engine->getCrankshaft(i)->m_body);\n        m_system->addConstraint(&m_crankConstraints[i]);\n        m_system->addConstraint(&m_crankshaftFrictionConstraints[i]);\n\n        if (crankshaft != outputShaft) {\n            atg_scs::ClutchConstraint *crankLink = &m_crankshaftLinks[i - 1];\n            crankLink->setBody1(&outputShaft->m_body);\n            crankLink->setBody2(&crankshaft->m_body);\n\n            m_system->addConstraint(crankLink);\n        }\n    }\n\n    m_transmission->addToSystem(m_system, &m_vehicleMass, m_vehicle, m_engine);\n    m_vehicle->addToSystem(m_system, &m_vehicleMass);\n\n    m_vehicleDrag.initialize(&m_vehicleMass, m_vehicle);\n    m_system->addConstraint(&m_vehicleDrag);\n\n    m_vehicleMass.reset();\n    m_vehicleMass.m = 1.0;\n    m_vehicleMass.I = 1.0;\n    m_system->addRigidBody(&m_vehicleMass);\n\n    for (int i = 0; i < cylinderCount; ++i) {\n        Piston *piston = m_engine->getPiston(i);\n        ConnectingRod *connectingRod = piston->getRod();\n\n        CylinderBank *bank = piston->getCylinderBank();\n        const double dx = std::cos(bank->getAngle() + constants::pi / 2);\n        const double dy = std::sin(bank->getAngle() + constants::pi / 2);\n\n        m_cylinderWallConstraints[i].setBody(&piston->m_body);\n        m_cylinderWallConstraints[i].m_dx = dx;\n        m_cylinderWallConstraints[i].m_dy = dy;\n        m_cylinderWallConstraints[i].m_local_x = 0.0;\n        m_cylinderWallConstraints[i].m_local_y = piston->getWristPinLocation();\n        m_cylinderWallConstraints[i].m_p0_x = bank->getX();\n        m_cylinderWallConstraints[i].m_p0_y = bank->getY();\n        m_cylinderWallConstraints[i].m_ks = ks;\n        m_cylinderWallConstraints[i].m_kd = kd;\n\n        piston->setCylinderConstraint(&m_cylinderWallConstraints[i]);\n\n        m_linkConstraints[i * 2 + 0].setBody1(&connectingRod->m_body);\n        m_linkConstraints[i * 2 + 0].setBody2(&piston->m_body);\n        m_linkConstraints[i * 2 + 0]\n            .setLocalPosition1(0.0, connectingRod->getLittleEndLocal());\n        m_linkConstraints[i * 2 + 0].setLocalPosition2(0.0, piston->getWristPinLocation());\n        m_linkConstraints[i * 2 + 0].m_ks = ks;\n        m_linkConstraints[i * 2 + 0].m_kd = kd;\n\n        double journal_x = 0.0, journal_y = 0.0;\n        if (connectingRod->getMasterRod() == nullptr) {\n            Crankshaft *crankshaft = connectingRod->getCrankshaft();\n            crankshaft->getRodJournalPositionLocal(\n                connectingRod->getJournal(),\n                &journal_x,\n                &journal_y);\n            m_linkConstraints[i * 2 + 1].setBody2(&crankshaft->m_body);\n        }\n        else {\n            connectingRod->getMasterRod()->getRodJournalPositionLocal(\n                connectingRod->getJournal(),\n                &journal_x,\n                &journal_y);\n            m_linkConstraints[i * 2 + 1].setBody2(&connectingRod->getMasterRod()->m_body);\n        }\n\n        m_linkConstraints[i * 2 + 1].setBody1(&connectingRod->m_body);\n        m_linkConstraints[i * 2 + 1]\n            .setLocalPosition1(0.0, connectingRod->getBigEndLocal());\n        m_linkConstraints[i * 2 + 1]\n            .setLocalPosition2(journal_x, journal_y);\n        m_linkConstraints[i * 2 + 1].m_ks = ks;\n        m_linkConstraints[i * 2 + 0].m_kd = kd;\n\n        piston->m_body.m = piston->getMass();\n        piston->m_body.I = 1.0;\n\n        connectingRod->m_body.m = connectingRod->getMass();\n        connectingRod->m_body.I = connectingRod->getMomentOfInertia();\n\n        m_system->addRigidBody(&piston->m_body);\n        m_system->addRigidBody(&connectingRod->m_body);\n        m_system->addConstraint(&m_linkConstraints[i * 2 + 0]);\n        m_system->addConstraint(&m_linkConstraints[i * 2 + 1]);\n        m_system->addConstraint(&m_cylinderWallConstraints[i]);\n        m_system->addForceGenerator(m_engine->getChamber(i));\n    }\n\n    m_dyno.connectCrankshaft(m_engine->getOutputCrankshaft());\n    m_system->addConstraint(&m_dyno);\n\n    m_starterMotor.connectCrankshaft(m_engine->getOutputCrankshaft());\n    m_starterMotor.m_maxTorque = m_engine->getStarterTorque();\n    m_starterMotor.m_rotationSpeed = -m_engine->getStarterSpeed();\n    m_system->addConstraint(&m_starterMotor);\n\n    placeAndInitialize();\n    initializeSynthesizer();\n}\n\ndouble PistonEngineSimulator::getAverageOutputSignal() const {\n    double sum = 0.0;\n    for (int i = 0; i < m_engine->getExhaustSystemCount(); ++i) {\n        sum += m_engine->getExhaustSystem(i)->getSystem()->pressure();\n    }\n\n    return sum / m_engine->getExhaustSystemCount();\n}\n\nvoid PistonEngineSimulator::placeAndInitialize() {\n    const int cylinderCount = m_engine->getCylinderCount();\n    for (int i = 0; i < cylinderCount; ++i) {\n        ConnectingRod *rod = m_engine->getConnectingRod(i);\n\n        if (rod->getRodJournalCount() != 0) {\n            placeCylinder(i);\n        }\n    }\n\n    for (int i = 0; i < cylinderCount; ++i) {\n        placeCylinder(i);\n    }\n\n    for (int i = 0; i < cylinderCount; ++i) {\n        m_engine->getChamber(i)->m_system.initialize(\n            units::pressure(1.0, units::atm),\n            m_engine->getChamber(i)->getVolume(),\n            units::celcius(25.0)\n        );\n\n        Piston *piston = m_engine->getChamber(i)->getPiston();\n        CylinderHead *head = m_engine->getChamber(i)->getCylinderHead();\n        ExhaustSystem *exhaust = head->getExhaustSystem(piston->getCylinderIndex());\n        const double exhaustLength =\n            head->getHeaderPrimaryLength(piston->getCylinderIndex())\n            + exhaust->getLength();\n        const double speedOfSound = 343.0 * units::m / units::sec;\n        const double delay = exhaustLength / speedOfSound;\n        m_delayFilters[i].initialize(delay, 10000.0);\n    }\n\n    m_engine->getIgnitionModule()->reset();\n\n    m_exhaustFlowStagingBuffer = new double[m_engine->getExhaustSystemCount()];\n}\n\nvoid PistonEngineSimulator::placeCylinder(int i) {\n    ConnectingRod *rod = m_engine->getConnectingRod(i);\n    Piston *piston = m_engine->getPiston(i);\n    CylinderBank *bank = piston->getCylinderBank();\n\n    double p_x, p_y;\n    if (rod->getMasterRod() != nullptr) {\n        rod->getMasterRod()->getRodJournalPositionGlobal(rod->getJournal(), &p_x, &p_y);\n    }\n    else {\n        rod->getCrankshaft()->getRodJournalPositionGlobal(rod->getJournal(), &p_x, &p_y);\n    }\n\n    // (bank->m_x + bank->m_dx * s - p_x)^2 + (bank->m_y + bank->m_dy * s - p_y)^2 = (rod->m_length)^2\n    const double a = bank->getDx() * bank->getDx() + bank->getDy() * bank->getDy();\n    const double b = -2 * bank->getDx() * (p_x - bank->getX()) - 2 * bank->getDy() * (p_y - bank->getY());\n    const double c =\n        (p_x - bank->getX()) * (p_x - bank->getX())\n        + (p_y - bank->getY()) * (p_y - bank->getY())\n        - rod->getLength() * rod->getLength();\n\n    const double det = b * b - 4 * a * c;\n    if (det < 0) return;\n\n    const double sqrt_det = std::sqrt(det);\n    const double s0 = (-b + sqrt_det) / (2 * a);\n    const double s1 = (-b - sqrt_det) / (2 * a);\n\n    const double s = std::max(s0, s1);\n    if (s < 0) return;\n\n    const double e_x = s * bank->getDx() + bank->getX();\n    const double e_y = s * bank->getDy() + bank->getY();\n\n    const double theta = ((e_y - p_y) > 0)\n        ? std::acos((e_x - p_x) / rod->getLength())\n        : 2 * constants::pi - std::acos((e_x - p_x) / rod->getLength());\n    rod->m_body.theta = theta - constants::pi / 2;\n\n    double cl_x, cl_y;\n    rod->m_body.localToWorld(0, rod->getBigEndLocal(), &cl_x, &cl_y);\n    rod->m_body.p_x += p_x - cl_x;\n    rod->m_body.p_y += p_y - cl_y;\n\n    piston->m_body.p_x = e_x;\n    piston->m_body.p_y = e_y;\n    piston->m_body.theta = bank->getAngle() + constants::pi;\n}\n\nvoid PistonEngineSimulator::simulateStep_() {\n    const double timestep = getTimestep();\n    IgnitionModule *im = m_engine->getIgnitionModule();\n    im->update(timestep);\n\n    const int cylinderCount = m_engine->getCylinderCount();\n    for (int i = 0; i < cylinderCount; ++i) {\n        if (im->getIgnitionEvent(i)) {\n            m_engine->getChamber(i)->ignite();\n        }\n\n        m_engine->getChamber(i)->update(timestep);\n    }\n\n    for (int i = 0; i < cylinderCount; ++i) {\n        m_engine->getChamber(i)->resetLastTimestepExhaustFlow();\n        m_engine->getChamber(i)->resetLastTimestepIntakeFlow();\n    }\n\n    const int exhaustSystemCount = m_engine->getExhaustSystemCount();\n    const int intakeCount = m_engine->getIntakeCount();\n    const double fluidTimestep = timestep / m_fluidSimulationSteps;\n    for (int i = 0; i < m_fluidSimulationSteps; ++i) {\n        for (int j = 0; j < exhaustSystemCount; ++j) {\n            m_engine->getExhaustSystem(j)->process(fluidTimestep);\n        }\n\n        for (int j = 0; j < intakeCount; ++j) {\n            m_engine->getIntake(j)->process(fluidTimestep);\n            m_engine->getIntake(j)->m_flowRate += m_engine->getIntake(j)->m_flow;\n        }\n\n        for (int j = 0; j < cylinderCount; ++j) {\n            m_engine->getChamber(j)->flow(fluidTimestep);\n        }\n    }\n\n    im->resetIgnitionEvents();\n}\n\ndouble PistonEngineSimulator::getTotalExhaustFlow() const {\n    double totalFlow = 0.0;\n    for (int i = 0; i < m_engine->getCylinderCount(); ++i) {\n        totalFlow += m_engine->getChamber(i)->getLastTimestepExhaustFlow();\n    }\n\n    return totalFlow;\n}\n\nvoid PistonEngineSimulator::endFrame() {\n    Simulator::endFrame();\n\n    if (m_engine == nullptr) {\n        return;\n    }\n\n    const double frameTimestep = simulationSteps() * getTimestep();\n    const int cylinderCount = m_engine->getCylinderCount();\n    for (int i = 0; i < m_engine->getIntakeCount(); ++i) {\n        m_engine->getIntake(i)->m_flowRate /= frameTimestep;\n    }\n}\n\nvoid PistonEngineSimulator::destroy() {\n    if (m_system != nullptr) m_system->reset();\n\n    if (m_crankConstraints != nullptr) delete[] m_crankConstraints;\n    if (m_cylinderWallConstraints != nullptr) delete[] m_cylinderWallConstraints;\n    if (m_linkConstraints != nullptr) delete[] m_linkConstraints;\n    if (m_crankshaftFrictionConstraints != nullptr) delete[] m_crankshaftFrictionConstraints;\n    if (m_exhaustFlowStagingBuffer != nullptr) delete[] m_exhaustFlowStagingBuffer;\n    if (m_system != nullptr) delete m_system;\n    if (m_delayFilters != nullptr) delete[] m_delayFilters;\n\n    m_crankConstraints = nullptr;\n    m_cylinderWallConstraints = nullptr;\n    m_linkConstraints = nullptr;\n    m_crankshaftFrictionConstraints = nullptr;\n    m_exhaustFlowStagingBuffer = nullptr;\n    m_system = nullptr;\n\n    m_vehicle = nullptr;\n    m_transmission = nullptr;\n    m_engine = nullptr;\n    m_delayFilters = nullptr;\n}\n\nvoid PistonEngineSimulator::writeToSynthesizer() {\n    const int exhaustSystemCount = m_engine->getExhaustSystemCount();\n    for (int i = 0; i < exhaustSystemCount; ++i) {\n        m_exhaustFlowStagingBuffer[i] = 0;\n    }\n\n    const double attenuation = std::min(std::abs(filteredEngineSpeed()), 40.0) / 40.0;\n    const double attenuation_3 = attenuation * attenuation * attenuation;\n\n    static double lastValveLift[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };\n\n    const double timestep = getTimestep();\n    const int cylinderCount = m_engine->getCylinderCount();\n    for (int i = 0; i < cylinderCount; ++i) {\n        Piston *piston = m_engine->getPiston(i);\n        CylinderBank *bank = piston->getCylinderBank();\n        CylinderHead *head = m_engine->getHead(bank->getIndex());\n        ExhaustSystem *exhaust = head->getExhaustSystem(piston->getCylinderIndex());\n        CombustionChamber *chamber = m_engine->getChamber(i);\n\n        const double exhaustLength =\n            head->getHeaderPrimaryLength(piston->getCylinderIndex())\n            + exhaust->getLength();\n\n        double exhaustFlow =\n            attenuation_3 * 1600 * (\n                1.0 * (chamber->m_exhaustRunnerAndPrimary.pressure() - units::pressure(1.0, units::atm))\n                + 0.1 * chamber->m_exhaustRunnerAndPrimary.dynamicPressure(1.0, 0.0)\n                + 0.1 * chamber->m_exhaustRunnerAndPrimary.dynamicPressure(-1.0, 0.0));\n\n        lastValveLift[i] = head->exhaustValveLift(piston->getCylinderIndex());\n\n        const double delayedExhaustPulse =\n            m_delayFilters[i].fast_f(exhaustFlow);\n\n        ExhaustSystem *exhaustSystem = head->getExhaustSystem(piston->getCylinderIndex());\n        m_exhaustFlowStagingBuffer[exhaustSystem->getIndex()] +=\n            head->getSoundAttenuation(piston->getCylinderIndex())\n            * (exhaustSystem->getAudioVolume() * delayedExhaustPulse / cylinderCount)\n            * (1 / (exhaustLength * exhaustLength));\n    }\n\n    synthesizer().writeInput(m_exhaustFlowStagingBuffer);\n}\n"
  },
  {
    "path": "src/piston_object.cpp",
    "content": "#include \"../include/piston_object.h\"\n\n#include \"../include/cylinder_bank.h\"\n#include \"../include/engine_sim_application.h\"\n\nPistonObject::PistonObject() {\n    m_piston = nullptr;\n    m_wristPinHole = {};\n}\n\nPistonObject::~PistonObject() {\n    /* void */\n}\n\nvoid PistonObject::generateGeometry() {\n    GeometryGenerator *gen = m_app->getGeometryGenerator();\n\n    GeometryGenerator::Circle2dParameters circleParams;\n    circleParams.center_x = 0.0f;\n    circleParams.center_y = (float)m_piston->getWristPinLocation();\n    circleParams.maxEdgeLength = m_app->pixelsToUnits(5.0f);\n    circleParams.radius = (float)(m_piston->getCylinderBank()->getBore() / 10) * 0.75f;\n    gen->startShape();\n    gen->generateCircle2d(circleParams);\n    gen->endShape(&m_wristPinHole);\n}\n\nvoid PistonObject::render(const ViewParameters *view) {\n    const int layer = m_piston->getRod()->getLayer();\n    if (layer > view->Layer1 || layer < view->Layer0) return;\n\n    const ysVector col = tintByLayer(m_app->getForegroundColor(), layer - view->Layer0);\n    const ysVector holeCol = tintByLayer(m_app->getBackgroundColor(), layer - view->Layer0);\n\n    resetShader();\n    setTransform(\n        &m_piston->m_body,\n        (float)(m_piston->getCylinderBank()->getBore() / 2),\n        0.0f,\n        (float)(-m_piston->getCompressionHeight() - m_piston->getWristPinLocation()));\n\n    m_app->getShaders()->SetBaseColor(col);\n    m_app->getEngine()->DrawModel(\n        m_app->getShaders()->GetRegularFlags(),\n        m_app->getAssetManager()->GetModelAsset(\"Piston\"),\n        0x32 - layer);\n\n    setTransform(&m_piston->m_body);\n    m_app->getShaders()->SetBaseColor(holeCol);\n    m_app->drawGenerated(m_wristPinHole, 0x32 - layer);\n}\n\nvoid PistonObject::process(float dt) {\n    /* void */\n}\n\nvoid PistonObject::destroy() {\n    /* void */\n}\n"
  },
  {
    "path": "src/right_gauge_cluster.cpp",
    "content": "#include \"../include/right_gauge_cluster.h\"\n\n#include \"../include/units.h\"\n#include \"../include/gauge.h\"\n#include \"../include/constants.h\"\n#include \"../include/engine_sim_application.h\"\n\n#include <cmath>\n#include <sstream>\n\nRightGaugeCluster::RightGaugeCluster() {\n    m_engine = nullptr;\n    m_simulator = nullptr;\n\n    m_afrCluster = nullptr;\n    m_tachometer = nullptr;\n    m_speedometer = nullptr;\n    m_manifoldVacuumGauge = nullptr;\n    m_volumetricEffGauge = nullptr;\n    m_intakeCfmGauge = nullptr;\n    m_combusionChamberStatus = nullptr;\n    m_throttleDisplay = nullptr;\n    m_fuelCluster = nullptr;\n    m_isAbsolute = false;\n}\n\nRightGaugeCluster::~RightGaugeCluster() {\n    /* void */\n}\n\nvoid RightGaugeCluster::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n\n    m_tachometer = addElement<LabeledGauge>();\n    m_speedometer = addElement<LabeledGauge>();\n    m_manifoldVacuumGauge = addElement<LabeledGauge>();\n    m_intakeCfmGauge = addElement<LabeledGauge>();\n    m_volumetricEffGauge = addElement<LabeledGauge>();\n    m_combusionChamberStatus = addElement<FiringOrderDisplay>();\n    m_throttleDisplay = addElement<ThrottleDisplay>();\n    m_afrCluster = addElement<AfrCluster>();\n    m_fuelCluster = addElement<FuelCluster>();\n\n    m_speedUnits = app->getAppSettings()->speedUnits;\n    m_pressureUnits = app->getAppSettings()->pressureUnits;\n    m_combusionChamberStatus->m_engine = m_engine;\n\n    constexpr float shortenAngle = (float)units::angle(1.0, units::deg);\n\n    m_tachometer->m_title = \"ENGINE SPEED\";\n    m_tachometer->m_unit = \"rpm\";\n    m_tachometer->m_precision = 0;\n    m_tachometer->setLocalPosition({ 0, 0 });\n    m_tachometer->m_gauge->m_min = 0;\n    m_tachometer->m_gauge->m_max = 7000;\n    m_tachometer->m_gauge->m_minorStep = 100;\n    m_tachometer->m_gauge->m_majorStep = 1000;\n    m_tachometer->m_gauge->m_maxMinorTick = INT_MAX;\n    m_tachometer->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_tachometer->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_tachometer->m_gauge->m_needleWidth = 4.0f;\n    m_tachometer->m_gauge->m_gamma = 1.0f;\n    m_tachometer->m_gauge->m_needleKs = 1000.0f;\n    m_tachometer->m_gauge->m_needleKd = 20.0f;\n    m_tachometer->m_gauge->setBandCount(3);\n    m_tachometer->m_gauge->setBand(\n        { m_app->getForegroundColor(), 400, 1000, 3.0f, 6.0f }, 0);\n    m_tachometer->m_gauge->setBand(\n        { m_app->getOrange(), 5000, 5500, 3.0f, 6.0f, -shortenAngle, shortenAngle }, 1);\n    m_tachometer->m_gauge->setBand(\n        { m_app->getRed(), 5500, 7000, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 2);\n\n    m_speedometer->m_title = \"VEHICLE SPEED\";\n    m_speedometer->m_unit = \"MPH\";\n\n    m_speedometer->m_precision = 0;\n    m_speedometer->setLocalPosition({ 0, 0 });\n    m_speedometer->m_gauge->m_min = 0;\n    m_speedometer->m_gauge->m_max = 200;\n    m_speedometer->m_gauge->m_minorStep = 5;\n    m_speedometer->m_gauge->m_majorStep = 10;\n    m_speedometer->m_gauge->m_maxMinorTick = 200;\n    m_speedometer->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_speedometer->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_speedometer->m_gauge->m_needleWidth = 4.0f;\n    m_speedometer->m_gauge->m_gamma = 1.0f;\n    m_speedometer->m_gauge->m_needleKs = 1000.0f;\n    m_speedometer->m_gauge->m_needleKd = 20.0f;\n    m_speedometer->m_gauge->setBandCount(0);\n\n    m_manifoldVacuumGauge->m_title = \"MANIFOLD PRESSURE\";\n    m_manifoldVacuumGauge->m_unit = \"inHg\";\n    m_manifoldVacuumGauge->m_precision = 0;\n    m_manifoldVacuumGauge->setLocalPosition({ 0, 0 });\n    m_manifoldVacuumGauge->m_gauge->m_min = -30;\n    m_manifoldVacuumGauge->m_gauge->m_max = 5;\n    m_manifoldVacuumGauge->m_gauge->m_minorStep = 1;\n    m_manifoldVacuumGauge->m_gauge->m_majorStep = 5;\n    m_manifoldVacuumGauge->m_gauge->m_maxMinorTick = 200;\n    m_manifoldVacuumGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_manifoldVacuumGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_manifoldVacuumGauge->m_gauge->m_needleWidth = 4.0f;\n    m_manifoldVacuumGauge->m_gauge->m_gamma = 1.0f;\n    m_manifoldVacuumGauge->m_gauge->m_needleKs = 1000.0f;\n    m_manifoldVacuumGauge->m_gauge->m_needleKd = 50.0f;\n    m_manifoldVacuumGauge->m_gauge->setBandCount(5);\n    m_manifoldVacuumGauge->m_gauge->setBand(\n        { m_app->getRed(), -5, -1, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0);\n    m_manifoldVacuumGauge->m_gauge->setBand(\n        { m_app->getForegroundColor(), -1.0f, 1.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1);\n    m_manifoldVacuumGauge->m_gauge->setBand(\n        { m_app->getOrange(), -10, -5, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2);\n    m_manifoldVacuumGauge->m_gauge->setBand(\n        { m_app->getBlue(), -22, -10, 3.0f, 6.0f, shortenAngle, shortenAngle }, 3);\n    m_manifoldVacuumGauge->m_gauge->setBand(\n        { m_app->getForegroundColor(), -30, -22, 3.0f, 6.0f, shortenAngle, shortenAngle }, 4);\n\n    m_volumetricEffGauge->m_title = \"VOLUMETRIC EFF.\";\n    m_volumetricEffGauge->m_unit = \"%\";\n    m_volumetricEffGauge->m_spaceBeforeUnit = false;\n    m_volumetricEffGauge->m_precision = 1;\n    m_volumetricEffGauge->setLocalPosition({ 0, 0 });\n    m_volumetricEffGauge->m_gauge->m_min = 0;\n    m_volumetricEffGauge->m_gauge->m_max = 120;\n    m_volumetricEffGauge->m_gauge->m_minorStep = 5;\n    m_volumetricEffGauge->m_gauge->m_majorStep = 10;\n    m_volumetricEffGauge->m_gauge->m_maxMinorTick = 200;\n    m_volumetricEffGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_volumetricEffGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_volumetricEffGauge->m_gauge->m_needleWidth = 4.0f;\n    m_volumetricEffGauge->m_gauge->m_gamma = 1.0f;\n    m_volumetricEffGauge->m_gauge->m_needleKs = 1000.0f;\n    m_volumetricEffGauge->m_gauge->m_needleKd = 50.0f;\n    m_volumetricEffGauge->m_gauge->setBandCount(3);\n    m_volumetricEffGauge->m_gauge->setBand(\n        { m_app->getBlue(), 30, 80, 3.0f, 6.0f, 0.0f, shortenAngle }, 0);\n    m_volumetricEffGauge->m_gauge->setBand(\n        { m_app->getGreen(), 80, 100, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1);\n    m_volumetricEffGauge->m_gauge->setBand(\n        { m_app->getRed(), 100, 120, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 2);\n\n    m_intakeCfmGauge->m_title = \"AIR SCFM\";\n    m_intakeCfmGauge->m_unit = \"\";\n    m_intakeCfmGauge->m_precision = 1;\n    m_intakeCfmGauge->setLocalPosition({ 0, 0 });\n    m_intakeCfmGauge->m_gauge->m_min = 0;\n    m_intakeCfmGauge->m_gauge->m_max = 1200;\n    m_intakeCfmGauge->m_gauge->m_minorStep = 20;\n    m_intakeCfmGauge->m_gauge->m_majorStep = 100;\n    m_intakeCfmGauge->m_gauge->m_maxMinorTick = 1200;\n    m_intakeCfmGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f;\n    m_intakeCfmGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f;\n    m_intakeCfmGauge->m_gauge->m_needleWidth = 4.0f;\n    m_intakeCfmGauge->m_gauge->m_gamma = 1.0f;\n    m_intakeCfmGauge->m_gauge->m_needleKs = 1000.0f;\n    m_intakeCfmGauge->m_gauge->m_needleKd = 50.0f;\n    m_intakeCfmGauge->m_gauge->setBandCount(0);\n    //Set display units\n    setUnits();\n}\n\nvoid RightGaugeCluster::destroy() {\n    UiElement::destroy();\n}\n\nvoid RightGaugeCluster::update(float dt) {\n    m_combusionChamberStatus->m_engine = m_engine;\n    m_throttleDisplay->m_engine = m_engine;\n    m_afrCluster->m_engine = m_engine;\n    m_fuelCluster->m_engine = m_engine;\n    m_fuelCluster->m_simulator = m_simulator;\n\n    UiElement::update(dt);\n}\n\nvoid RightGaugeCluster::render() {\n    drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor());\n\n    const Bounds tachSpeedCluster = m_bounds.verticalSplit(0.5f, 1.0f);\n    renderTachSpeedCluster(tachSpeedCluster);\n\n    const Bounds fuelAirCluster = m_bounds.verticalSplit(0.0f, 0.5f);\n    renderFuelAirCluster(fuelAirCluster);\n\n    UiElement::render();\n}\n\nvoid RightGaugeCluster::setEngine(Engine *engine) {\n    m_engine = engine;\n}\n\nvoid RightGaugeCluster::renderTachSpeedCluster(const Bounds &bounds) {\n    const Bounds left = bounds.horizontalSplit(0.0f, 0.5f);\n    const Bounds right = bounds.horizontalSplit(0.5f, 1.0f);\n\n    const Bounds tach = left.verticalSplit(0.5f, 1.0f);\n    m_tachometer->m_bounds = tach;\n    m_tachometer->m_gauge->m_value = (float)std::abs(getRpm());\n\n    constexpr float shortenAngle = (float)units::angle(1.0, units::deg);\n    const float maxRpm =\n        (float)std::ceil(units::toRpm(getRedline() * 1.25) / 1000.0) * 1000.0f;\n    const float redline =\n        (float)std::ceil(units::toRpm(getRedline()) / 500.0) * 500.0f;\n    const float redlineWarning =\n        (float)std::floor(units::toRpm(getRedline() * 0.9) / 500.0) * 500.0f;\n    m_tachometer->m_gauge->m_max = (int)maxRpm;\n    m_tachometer->m_gauge->setBandCount(3);\n    m_tachometer->m_gauge->setBand(\n        { m_app->getForegroundColor(), 400, 1000, 3.0f, 6.0f }, 0);\n    m_tachometer->m_gauge->setBand(\n        { m_app->getOrange(), redlineWarning, redline, 3.0f, 6.0f, -shortenAngle, shortenAngle }, 1);\n    m_tachometer->m_gauge->setBand(\n        { m_app->getRed(), redline, maxRpm, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 2);\n\n    const Bounds speed = left.verticalSplit(0.0f, 0.5f);\n    m_speedometer->m_bounds = speed;\n\n    m_speedometer->m_gauge->m_value = (m_speedUnits == \"mph\") \n        ? (float)units::convert(std::abs(getSpeed()), units::mile / units::hour) \n        : (float)units::convert(std::abs(getSpeed()), units::km / units::hour);\n\n\n    m_combusionChamberStatus->m_bounds = right;\n}\n\nvoid RightGaugeCluster::renderFuelAirCluster(const Bounds &bounds) {\n    const Bounds left = bounds.horizontalSplit(0.0f, 0.5f);\n    const Bounds right = bounds.horizontalSplit(0.5f, 1.0f);\n\n    const Bounds throttle = left.verticalSplit(0.5f, 1.0f);\n    m_throttleDisplay->m_bounds = throttle;\n\n    const Bounds fuelSection = left.verticalSplit(0.0f, 0.5f);\n    const Bounds afr = fuelSection.horizontalSplit(0.0f, 0.5f);\n    m_afrCluster->m_bounds = afr;\n\n    const Bounds fuelConsumption = fuelSection.horizontalSplit(0.5f, 1.0f);\n    m_fuelCluster->m_bounds = fuelConsumption;\n\n    constexpr double ambientPressure = units::pressure(1.0, units::atm);\n    constexpr double ambientTemperature = units::celcius(25.0);\n\n    Grid grid = { 1, 3 };\n    const Bounds manifoldVacuum = grid.get(right, 0, 0, 1, 1);\n    m_manifoldVacuumGauge->m_bounds = manifoldVacuum;\n\n    const double vacuumReading = getManifoldPressureWithUnits(ambientPressure);\n    if (m_isAbsolute) {\n        m_manifoldVacuumGauge->m_gauge->m_value = static_cast<float>(vacuumReading);\n    }\n    else {\n        m_manifoldVacuumGauge->m_gauge->m_value = (vacuumReading > -0.5)\n            ? 0.0f\n            : static_cast<float>(vacuumReading);\n    }\n\n    const double rpm = std::fmax(getRpm(), 0.0);\n    const double theoreticalAirPerRevolution = (m_engine == nullptr)\n        ? 0.0\n        : 0.5 * (ambientPressure * m_engine->getDisplacement())\n            / (constants::R * ambientTemperature);\n    const double theoreticalAirPerSecond = theoreticalAirPerRevolution * rpm / 60.0;\n    const double actualAirPerSecond = (m_engine == nullptr)\n        ? 0.0\n        : m_engine->getIntakeFlowRate();\n    const double volumetricEfficiency = (std::abs(theoreticalAirPerSecond) < 1E-3)\n        ? 0.0\n        : (actualAirPerSecond / theoreticalAirPerSecond);\n\n    const Bounds cfmBounds = grid.get(right, 0, 1, 1, 1);\n    m_intakeCfmGauge->m_bounds = cfmBounds;\n    m_intakeCfmGauge->m_gauge->m_value =\n        (float)units::convert(actualAirPerSecond, units::scfm);\n\n    const Bounds volumetricEfficiencyBounds = grid.get(right, 0, 2, 1, 1);\n    m_volumetricEffGauge->m_bounds = volumetricEfficiencyBounds;\n    m_volumetricEffGauge->m_gauge->m_value = 100.0f * (float)volumetricEfficiency;\n}\n\ndouble RightGaugeCluster::getManifoldPressureWithUnits(double ambientPressure) {\n    if (m_pressureUnits == \"inHg\") {\n        return units::convert(std::fmin(getManifoldPressure() - ambientPressure, 0.0), units::inHg);\n    }\n    else if (m_pressureUnits == \"kPa\") {\n        return units::convert(getManifoldPressure(), units::kPa);\n    }\n    else if (m_pressureUnits == \"mbar\") {\n        return units::convert(getManifoldPressure(), units::mbar);\n    }\n    else if (m_pressureUnits == \"bar\") {\n        return units::convert(getManifoldPressure(), units::bar);\n    }\n    else if (m_pressureUnits == \"psi\") {\n        return units::convert(std::fmin(getManifoldPressure() - ambientPressure, 0.0), units::psi);\n    }\n    else {\n        return units::convert(std::fmin(getManifoldPressure() - ambientPressure, 0.0), units::inHg);\n    }\n}\n\ndouble RightGaugeCluster::getRpm() const {\n    return (m_engine != nullptr)\n        ? m_engine->getRpm()\n        : 0;\n}\n\ndouble RightGaugeCluster::getRedline() const {\n    return (m_engine != nullptr)\n        ? m_engine->getRedline()\n        : 0;\n}\n\ndouble RightGaugeCluster::getSpeed() const {\n    return (m_simulator->getVehicle() != nullptr)\n        ? m_simulator->getVehicle()->getSpeed()\n        : 0;\n}\n\ndouble RightGaugeCluster::getManifoldPressure() const {\n    return (m_engine != nullptr)\n        ? m_engine->getManifoldPressure()\n        : units::pressure(1.0, units::atm);\n}\n\nvoid RightGaugeCluster::setUnits() {\n    constexpr float shortenAngle = (float)units::angle(1.0, units::deg);\n\n    m_speedometer->m_unit = (m_speedUnits == \"mph\")\n        ? \"mph\"\n        : \"kph\";\n\n    if (m_pressureUnits == \"kPa\") {\n        m_isAbsolute = true;\n\n        m_manifoldVacuumGauge->m_unit = \"kPa\";\n        m_manifoldVacuumGauge->m_gauge->m_min = 0;\n        m_manifoldVacuumGauge->m_gauge->m_max = 110;\n        m_manifoldVacuumGauge->m_gauge->m_minorStep = 5;\n        m_manifoldVacuumGauge->m_gauge->m_majorStep = 10;\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getRed(), 110, 90, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getForegroundColor(), -1.0f, 1.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getOrange(), 30, 40, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getBlue(), 15, 29, 3.0f, 6.0f, shortenAngle, shortenAngle }, 3);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getForegroundColor(), 0, 14, 3.0f, 6.0f, shortenAngle, shortenAngle }, 4);\n    }\n    else if (m_pressureUnits == \"mbar\") {\n        m_isAbsolute = true;\n\n        m_manifoldVacuumGauge->m_unit = \"mbar\";\n        m_manifoldVacuumGauge->m_gauge->m_min = 0;\n        m_manifoldVacuumGauge->m_gauge->m_max = 1100;\n        m_manifoldVacuumGauge->m_gauge->m_minorStep = 50;\n        m_manifoldVacuumGauge->m_gauge->m_majorStep = 100;\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getRed(), 1100, 900, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getForegroundColor(), -1.0f, 1.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getOrange(), 300, 400, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getBlue(), 150, 290, 3.0f, 6.0f, shortenAngle, shortenAngle }, 3);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getForegroundColor(), 0, 140, 3.0f, 6.0f, shortenAngle, shortenAngle }, 4);\n    }\n    else if (m_pressureUnits == \"bar\") {\n        m_isAbsolute = true;\n\n        m_manifoldVacuumGauge->m_unit = \"bar\";\n        m_manifoldVacuumGauge->m_gauge->m_min = 0;\n        m_manifoldVacuumGauge->m_gauge->m_max = 1.1f;\n        m_manifoldVacuumGauge->m_gauge->m_minorStep = 1;\n        m_manifoldVacuumGauge->m_gauge->m_majorStep = 1;\n        m_manifoldVacuumGauge->m_precision = 2;\n\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getRed(), 0.8f, 1.1f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getForegroundColor(), -1.0f, 1.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getOrange(), 0.3f, 0.5f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getBlue(), 0.15f, 0.29f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 3);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getForegroundColor(), 0, 0.14f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 4);\n    }\n    else if (m_pressureUnits == \"psi\") {\n        m_isAbsolute = false;\n\n        m_manifoldVacuumGauge->m_unit = \"psi\";\n        m_manifoldVacuumGauge->m_gauge->m_min = -15;\n        m_manifoldVacuumGauge->m_gauge->m_max = 3;\n        m_manifoldVacuumGauge->m_gauge->m_minorStep = 1;\n        m_manifoldVacuumGauge->m_gauge->m_majorStep = 5;\n        m_manifoldVacuumGauge->m_precision = 1;\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getRed(), -4, 1, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getForegroundColor(), -1.0f, 1.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getOrange(), -7, -4, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getBlue(), -12, -7, 3.0f, 6.0f, shortenAngle, shortenAngle }, 3);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getForegroundColor(), -15, -12, 3.0f, 6.0f, shortenAngle, shortenAngle }, 4);\n    }\n    else {\n        m_isAbsolute = false;\n\n        m_manifoldVacuumGauge->m_unit = \"inHg\";\n        m_manifoldVacuumGauge->m_gauge->m_min = -30;\n        m_manifoldVacuumGauge->m_gauge->m_max = 5;\n        m_manifoldVacuumGauge->m_gauge->m_minorStep = 1;\n        m_manifoldVacuumGauge->m_gauge->m_majorStep = 5;\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getRed(), -5, -1, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getForegroundColor(), -1.0f, 1.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getOrange(), -10, -5, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getBlue(), -22, -10, 3.0f, 6.0f, shortenAngle, shortenAngle }, 3);\n        m_manifoldVacuumGauge->m_gauge->setBand(\n            { m_app->getForegroundColor(), -30, -22, 3.0f, 6.0f, shortenAngle, shortenAngle }, 4);\n    }\n}\n"
  },
  {
    "path": "src/shaders.cpp",
    "content": "#include \"../include/shaders.h\"\n\nShaders::Shaders() {\n    m_cameraPosition = ysMath::LoadVector(0.0f, 0.0f);\n\n    m_mainStage = nullptr;\n    m_uiStage = nullptr;\n\n    m_objectVariables.ColorReplace = 1;\n    m_objectVariables.Lit = 0;\n    m_objectVariables.Transform = ysMath::LoadIdentity();\n\n    m_screenVariables.FogNear = m_uiScreenVariables.FogNear = 16000.0f;\n    m_screenVariables.FogFar = m_uiScreenVariables.FogFar = 16001.0f;\n}\n\nShaders::~Shaders() {\n    /* void */\n}\n\nysError Shaders::Initialize(\n        dbasic::ShaderSet *shaderSet,\n        ysRenderTarget *mainRenderTarget,\n        ysRenderTarget *uiRenderTarget,\n        ysShaderProgram *shaderProgram,\n        ysInputLayout *inputLayout)\n{\n    YDS_ERROR_DECLARE(\"Initialize\");\n\n    YDS_NESTED_ERROR_CALL(shaderSet->NewStage(\"ShaderStage::Main\", &m_mainStage));\n    YDS_NESTED_ERROR_CALL(shaderSet->NewStage(\"ShaderStage::UI\", &m_uiStage));\n\n    m_mainStage->SetInputLayout(inputLayout);\n    m_mainStage->SetRenderTarget(mainRenderTarget);\n    m_mainStage->SetShaderProgram(shaderProgram);\n    m_mainStage->SetFlagBit(0);\n    m_mainStage->SetType(dbasic::ShaderStage::Type::FullPass);\n\n    m_mainStage->NewConstantBuffer<dbasic::ShaderScreenVariables>(\n        \"Buffer::ScreenData\",\n        0,\n        dbasic::ShaderStage::ConstantBufferBinding::BufferType::SceneData,\n        &m_screenVariables);\n    m_mainStage->NewConstantBuffer<dbasic::ShaderObjectVariables>(\n        \"Buffer::ObjectData\",\n        1,\n        dbasic::ShaderStage::ConstantBufferBinding::BufferType::ObjectData,\n        &m_objectVariables);\n    m_mainStage->NewConstantBuffer<dbasic::LightingControls>(\n        \"Buffer::LightingData\",\n        3,\n        dbasic::ShaderStage::ConstantBufferBinding::BufferType::SceneData,\n        &m_lightingControls);\n\n    // UI Stage\n    m_uiStage->SetInputLayout(inputLayout);\n    m_uiStage->SetRenderTarget(uiRenderTarget);\n    m_uiStage->SetShaderProgram(shaderProgram);\n    m_uiStage->SetFlagBit(1);\n    m_uiStage->SetClearTarget(false);\n    m_uiStage->SetType(dbasic::ShaderStage::Type::FullPass);\n\n    m_uiStage->NewConstantBuffer<dbasic::ShaderScreenVariables>(\n        \"Buffer::ScreenData\",\n        0,\n        dbasic::ShaderStage::ConstantBufferBinding::BufferType::SceneData,\n        &m_uiScreenVariables);\n    m_uiStage->NewConstantBuffer<dbasic::ShaderObjectVariables>(\n        \"Buffer::ObjectData\",\n        1,\n        dbasic::ShaderStage::ConstantBufferBinding::BufferType::ObjectData,\n        &m_objectVariables);\n    m_uiStage->NewConstantBuffer<dbasic::LightingControls>(\n        \"Buffer::LightingData\",\n        3,\n        dbasic::ShaderStage::ConstantBufferBinding::BufferType::SceneData,\n        &m_lightingControls);\n\n    return YDS_ERROR_RETURN(ysError::None);\n}\n\nysError Shaders::UseMaterial(dbasic::Material *material) {\n    YDS_ERROR_DECLARE(\"UseMaterial\");\n    return YDS_ERROR_RETURN(ysError::None);\n}\n\nvoid Shaders::SetObjectTransform(const ysMatrix &mat) {\n    m_objectVariables.Transform = mat;\n}\n\nvoid Shaders::ConfigureModel(float scale, dbasic::ModelAsset *model) {\n    /* void */\n}\n\nvoid Shaders::SetBaseColor(const ysVector &color) {\n    m_objectVariables.BaseColor = color;\n}\n\nvoid Shaders::ResetBaseColor() {\n    m_objectVariables.BaseColor = ysMath::LoadVector(1.0f, 1.0f, 1.0f, 1.0f);\n}\n\ndbasic::StageEnableFlags Shaders::GetRegularFlags() const {\n    return m_mainStage->GetFlags();\n}\n\ndbasic::StageEnableFlags Shaders::GetUiFlags() const {\n    return m_uiStage->GetFlags();\n}\n\nvoid Shaders::CalculateCamera(\n    float width,\n    float height,\n    const Bounds &cameraBounds,\n    float screenWidth,\n    float screenHeight,\n    float angle)\n{\n    const ysMatrix projection = ysMath::OrthographicProjection(\n        width,\n        height,\n        0.001f,\n        500.0f);\n    const Point scale = Point(screenWidth, screenHeight);\n    const Point center =\n        (cameraBounds.getPosition() - Point(screenWidth / 2, screenHeight / 2))\n        / scale;\n\n    m_screenVariables.Projection = ysMath::Transpose(\n        ysMath::MatMult(\n            projection,\n            ysMath::MatMult(\n                ysMath::ScaleTransform(ysMath::LoadVector(\n                    cameraBounds.width() / screenWidth,\n                    cameraBounds.height() / screenHeight,\n                    1.0f)),\n                ysMath::TranslationTransform(ysMath::LoadVector(center.x, center.y, 0.0f))\n            )\n        )\n    );\n\n    m_screenVariables.Projection = ysMath::Transpose(projection);\n\n    const float sinAngle = std::sin(angle);\n    const float cosAngle = std::cos(angle);\n\n    const ysVector cameraEye =\n        ysMath::Add(\n                ysMath::LoadVector(10.0f * sinAngle, 0.0f, 10.0f * cosAngle, 1.0f),\n                m_cameraPosition);\n    const ysVector cameraTarget =\n        ysMath::Add(\n                ysMath::LoadVector(0.0f, 0.0f, 0.0f, 1.0f),\n                m_cameraPosition);\n    const ysVector up = ysMath::LoadVector(0.0f, 1.0f);\n\n    m_screenVariables.CameraView =\n        ysMath::Transpose(ysMath::CameraTarget(cameraEye, cameraTarget, up));\n    m_screenVariables.Eye = ysMath::LoadVector(cameraEye);\n}\n\nvoid Shaders::CalculateUiCamera(float screenWidth, float screenHeight) {\n    m_uiScreenVariables.Projection = ysMath::Transpose(\n        ysMath::OrthographicProjection(\n            screenWidth,\n            screenHeight,\n            0.001f,\n            500.0f));\n\n    const ysVector cameraEye =\n        ysMath::LoadVector(0.0f, 0.0f, 10.0f, 1.0f);\n    const ysVector cameraTarget =\n        ysMath::LoadVector(0.0f, 0.0f, 0.0f, 1.0f);\n    const ysVector up = ysMath::LoadVector(0.0f, 1.0f);\n\n    m_uiScreenVariables.CameraView =\n        ysMath::Transpose(ysMath::CameraTarget(cameraEye, cameraTarget, up));\n    m_uiScreenVariables.Eye = ysMath::LoadVector(cameraEye);\n}\n\nvoid Shaders::SetClearColor(const ysVector &col) {\n    m_mainStage->SetClearColor(col);\n}\n"
  },
  {
    "path": "src/simulation_object.cpp",
    "content": "#include \"../include/simulation_object.h\"\n\n#include \"../include/piston.h\"\n#include \"../include/cylinder_bank.h\"\n\n#include \"../include/engine_sim_application.h\"\n\nSimulationObject::SimulationObject() {\n    m_app = nullptr;\n}\n\nSimulationObject::~SimulationObject() {\n    /* void */\n}\n\nvoid SimulationObject::initialize(EngineSimApplication *app) {\n    m_app = app;\n}\n\nvoid SimulationObject::generateGeometry() {\n    /* void */\n}\n\nvoid SimulationObject::render(const ViewParameters *settings) {\n    /* void */\n}\n\nvoid SimulationObject::process(float dt) {\n    /* void */\n}\n\nvoid SimulationObject::destroy() {\n    /* void */\n}\n\nPiston *SimulationObject::getForemostPiston(CylinderBank *bank, int layer) {\n    Engine *engine = m_app->getSimulator()->getEngine();\n    Piston *frontmostPiston = nullptr;\n    const int cylinderCount = engine->getCylinderCount();\n    for (int i = 0; i < cylinderCount; ++i) {\n        Piston *piston = engine->getPiston(i);\n        if (piston->getCylinderBank() == bank) {\n            if (piston->getRod()->getJournal() >= layer) {\n                if (frontmostPiston == nullptr\n                    || piston->getRod()->getJournal() < frontmostPiston->getRod()->getJournal()) {\n                    frontmostPiston = piston;\n                }\n            }\n        }\n    }\n\n    return frontmostPiston;\n}\n\nvoid SimulationObject::resetShader() {\n    m_app->getShaders()->ResetBaseColor();\n    m_app->getShaders()->SetObjectTransform(ysMath::LoadIdentity());\n}\n\nvoid SimulationObject::setTransform(\n    atg_scs::RigidBody *rigidBody,\n    float scale,\n    float lx,\n    float ly,\n    float angle,\n    float z)\n{\n    double p_x, p_y;\n    rigidBody->localToWorld(lx, ly, &p_x, &p_y);\n\n    const ysMatrix rot = ysMath::RotationTransform(\n            ysMath::Constants::ZAxis,\n            (float)rigidBody->theta + angle);\n    const ysMatrix trans = ysMath::TranslationTransform(\n            ysMath::LoadVector((float)p_x, (float)p_y, z));\n    const ysMatrix scaleTransform = ysMath::ScaleTransform(ysMath::LoadScalar(scale));\n\n    m_app->getShaders()->SetObjectTransform(\n        ysMath::MatMult(ysMath::MatMult(trans, rot), scaleTransform));\n}\n\nysVector SimulationObject::tintByLayer(const ysVector &col, int layers) const {\n    ysVector result = col;\n    for (int i = 0; i < layers; ++i) {\n        result = ysMath::Add(\n            ysMath::Mul(result, ysMath::LoadScalar(0.3f)),\n            ysMath::Mul(m_app->getBackgroundColor(), ysMath::LoadScalar(0.7f))\n        );\n    }\n\n    return result;\n}\n"
  },
  {
    "path": "src/simulator.cpp",
    "content": "#include \"../include/simulator.h\"\n\nSimulator::Simulator() {\n    m_engine = nullptr;\n    m_vehicle = nullptr;\n    m_transmission = nullptr;\n    m_system = nullptr;\n\n    m_physicsProcessingTime = 0;\n\n    m_simulationSpeed = 1.0;\n    m_targetSynthesizerLatency = 0.1;\n    m_simulationFrequency = 10000;\n    m_steps = 0;\n\n    m_currentIteration = 0;\n\n    m_filteredEngineSpeed = 0.0;\n    m_dynoTorqueSamples = nullptr;\n    m_lastDynoTorqueSample = 0;\n}\n\nSimulator::~Simulator() {\n    assert(m_system == nullptr);\n    assert(m_dynoTorqueSamples == nullptr);\n}\n\nvoid Simulator::initialize(const Parameters &params) {\n    if (params.systemType == SystemType::NsvOptimized) {\n        atg_scs::OptimizedNsvRigidBodySystem *system =\n            new atg_scs::OptimizedNsvRigidBodySystem;\n        system->initialize(\n            new atg_scs::GaussSeidelSleSolver);\n        m_system = system;\n    }\n    else {\n        atg_scs::GenericRigidBodySystem *system =\n            new atg_scs::GenericRigidBodySystem;\n        system->initialize(\n            new atg_scs::GaussianEliminationSleSolver,\n            new atg_scs::NsvOdeSolver);\n        m_system = system;\n    }\n\n    m_dynoTorqueSamples = new double[DynoTorqueSamples];\n    for (int i = 0; i < DynoTorqueSamples; ++i) {\n        m_dynoTorqueSamples[i] = 0.0;\n    }\n}\n\nvoid Simulator::loadSimulation(Engine *engine, Vehicle *vehicle, Transmission *transmission) {\n    m_engine = engine;\n    m_vehicle = vehicle;\n    m_transmission = transmission;\n}\n\nvoid Simulator::releaseSimulation() {\n    m_synthesizer.endAudioRenderingThread();\n    if (m_system != nullptr) m_system->reset();\n\n    destroy();\n}\n\nvoid Simulator::startFrame(double dt) {\n    if (m_engine == nullptr) {\n        m_steps = 0;\n        return;\n    }\n\n    m_simulationStart = std::chrono::steady_clock::now();\n    m_currentIteration = 0;\n    m_synthesizer.setInputSampleRate(m_simulationFrequency * m_simulationSpeed);\n\n    const double timestep = getTimestep();\n    m_steps = (int)std::round((dt * m_simulationSpeed) / timestep);\n\n    const double targetLatency = getSynthesizerInputLatencyTarget();\n    if (m_synthesizer.getLatency() < targetLatency) {\n        m_steps = static_cast<int>((m_steps + 1) * 1.1);\n    }\n    else if (m_synthesizer.getLatency() > targetLatency) {\n        m_steps = static_cast<int>((m_steps - 1) * 0.9);\n        if (m_steps < 0) {\n            m_steps = 0;\n        }\n    }\n\n    if (m_steps > 0) {\n        for (int i = 0; i < m_engine->getIntakeCount(); ++i) {\n            m_engine->getIntake(i)->m_flowRate = 0;\n        }\n    }\n}\n\nbool Simulator::simulateStep() {\n    if (getCurrentIteration() >= simulationSteps()) {\n        auto s1 = std::chrono::steady_clock::now();\n\n        const long long lastFrame =\n            std::chrono::duration_cast<std::chrono::microseconds>(s1 - m_simulationStart).count();\n        m_physicsProcessingTime = m_physicsProcessingTime * 0.98 + 0.02 * lastFrame;\n\n        return false;\n    }\n\n    const double timestep = getTimestep();\n    m_system->process(timestep, 1);\n\n    m_engine->update(timestep);\n    m_vehicle->update(timestep);\n    m_transmission->update(timestep);\n\n    updateFilteredEngineSpeed(timestep);\n\n    Crankshaft *outputShaft = m_engine->getOutputCrankshaft();\n    outputShaft->resetAngle();\n\n    for (int i = 0; i < m_engine->getCrankshaftCount(); ++i) {\n        Crankshaft *shaft = m_engine->getCrankshaft(i);\n\n        // Correct drift (temporary hack)\n        shaft->m_body.theta = outputShaft->m_body.theta;\n    }\n\n    const int index =\n        static_cast<int>(std::floor(DynoTorqueSamples * outputShaft->getCycleAngle() / (4 * constants::pi)));\n    const int step = m_engine->isSpinningCw() ? 1 : -1;\n    m_dynoTorqueSamples[index] = m_dyno.getTorque();\n\n    if (m_lastDynoTorqueSample != index) {\n        for (int i = m_lastDynoTorqueSample + step; i != index; i += step) {\n            if (i >= DynoTorqueSamples) {\n                i = -1;\n                continue;\n            }\n            else if (i < 0) {\n                i = DynoTorqueSamples;\n                continue;\n            }\n\n            m_dynoTorqueSamples[i] = m_dyno.getTorque();\n        }\n\n        m_lastDynoTorqueSample = index;\n    }\n\n    simulateStep_();\n\n    writeToSynthesizer();\n\n    ++m_currentIteration;\n    return true;\n}\n\ndouble Simulator::getTotalExhaustFlow() const {\n    return 0.0;\n}\n\nint Simulator::readAudioOutput(int samples, int16_t *target) {\n    return m_synthesizer.readAudioOutput(samples, target);\n}\n\nvoid Simulator::endFrame() {\n    m_synthesizer.endInputBlock();\n}\n\nvoid Simulator::destroy() {\n    m_synthesizer.destroy();\n}\n\nvoid Simulator::startAudioRenderingThread() {\n    m_synthesizer.startAudioRenderingThread();\n}\n\nvoid Simulator::endAudioRenderingThread() {\n    m_synthesizer.endAudioRenderingThread();\n}\n\ndouble Simulator::getSynthesizerInputLatencyTarget() const {\n    return m_targetSynthesizerLatency;\n}\n\ndouble Simulator::getFilteredDynoTorque() const {\n    if (m_dynoTorqueSamples == nullptr) return 0;\n\n    double averageTorque = 0;\n    for (int i = 0; i < DynoTorqueSamples; ++i) {\n        averageTorque += m_dynoTorqueSamples[i];\n    }\n\n    return averageTorque / DynoTorqueSamples;\n}\n\ndouble Simulator::getDynoPower() const {\n    return (m_engine != nullptr)\n        ? getFilteredDynoTorque() * m_engine->getSpeed()\n        : 0;\n}\n\ndouble Simulator::getAverageOutputSignal() const {\n    return 0.0;\n}\n\nvoid Simulator::initializeSynthesizer() {\n    Synthesizer::Parameters synthParams;\n    synthParams.audioBufferSize = 44100;\n    synthParams.audioSampleRate = 44100;\n    synthParams.inputBufferSize = 44100;\n    synthParams.inputChannelCount = m_engine->getExhaustSystemCount();\n    synthParams.inputSampleRate = static_cast<float>(getSimulationFrequency());\n    m_synthesizer.initialize(synthParams);\n}\n\nvoid Simulator::simulateStep_() {\n}\n\nvoid Simulator::updateFilteredEngineSpeed(double dt) {\n    const double alpha = dt / (100 + dt);\n    m_filteredEngineSpeed = alpha * m_filteredEngineSpeed + (1 - alpha) * m_engine->getRpm();\n}\n"
  },
  {
    "path": "src/standard_valvetrain.cpp",
    "content": "#include \"../include/standard_valvetrain.h\"\n\n#include \"../include/camshaft.h\"\n\nStandardValvetrain::StandardValvetrain() {\n    m_intakeCamshaft = nullptr;\n    m_exhaustCamshaft = nullptr;\n}\n\nStandardValvetrain::~StandardValvetrain() {\n    /* void */\n}\n\nvoid StandardValvetrain::initialize(const Parameters &params) {\n    m_intakeCamshaft = params.intakeCamshaft;\n    m_exhaustCamshaft = params.exhaustCamshaft;\n}\n\ndouble StandardValvetrain::intakeValveLift(int cylinder) {\n    return m_intakeCamshaft->valveLift(cylinder);\n}\n\ndouble StandardValvetrain::exhaustValveLift(int cylinder) {\n    return m_exhaustCamshaft->valveLift(cylinder);\n}\n\nCamshaft *StandardValvetrain::getActiveIntakeCamshaft() {\n    return m_intakeCamshaft;\n}\n\nCamshaft *StandardValvetrain::getActiveExhaustCamshaft() {\n    return m_exhaustCamshaft;\n}\n"
  },
  {
    "path": "src/starter_motor.cpp",
    "content": "#include \"../include/starter_motor.h\"\n\n#include \"../include/units.h\"\n\nStarterMotor::StarterMotor() : atg_scs::Constraint(1, 1) {\n    m_ks = 10.0;\n    m_kd = 1.0;\n    m_maxTorque = units::torque(80.0, units::ft_lb);\n    m_rotationSpeed = -units::rpm(200.0);\n    m_enabled = false;\n}\n\nStarterMotor::~StarterMotor() {\n    /* void */\n}\n\nvoid StarterMotor::connectCrankshaft(Crankshaft *crankshaft) {\n    m_bodies[0] = &crankshaft->m_body;\n}\n\nvoid StarterMotor::calculate(Output *output, atg_scs::SystemState *state) {\n    output->J[0][0] = 0;\n    output->J[0][1] = 0;\n    output->J[0][2] = 1;\n\n    output->J_dot[0][0] = 0;\n    output->J_dot[0][1] = 0;\n    output->J_dot[0][2] = 0;\n\n    output->ks[0] = m_ks;\n    output->kd[0] = m_kd;\n\n    output->C[0] = 0;\n\n    output->v_bias[0] = -m_rotationSpeed;\n\n    if (m_rotationSpeed < 0) {\n        output->limits[0][0] = m_enabled ? -m_maxTorque : 0.0;\n        output->limits[0][1] = 0.0;\n    }\n    else {\n        output->limits[0][0] = 0.0;\n        output->limits[0][1] = m_enabled ? m_maxTorque : 0.0;\n    }\n}\n"
  },
  {
    "path": "src/synthesizer.cpp",
    "content": "#include \"../include/synthesizer.h\"\n\n#include \"../include/utilities.h\"\n#include \"../include/delta.h\"\n\n#include <cassert>\n#include <cmath>\n\n#undef min\n#undef max\n\nSynthesizer::Synthesizer() {\n    m_inputChannels = nullptr;\n    m_inputChannelCount = 0;\n    m_inputBufferSize = 0;\n    m_inputWriteOffset = 0.0;\n    m_inputSamplesRead = 0;\n\n    m_audioBufferSize = 0;\n\n    m_inputSampleRate = 0.0;\n    m_audioSampleRate = 0.0;\n\n    m_lastInputSampleOffset = 0.0;\n\n    m_run = true;\n    m_thread = nullptr;\n    m_filters = nullptr;\n}\n\nSynthesizer::~Synthesizer() {\n    assert(m_inputChannels == nullptr);\n    assert(m_thread == nullptr);\n    assert(m_filters == nullptr);\n}\n\nvoid Synthesizer::initialize(const Parameters &p) {\n    m_inputChannelCount = p.inputChannelCount;\n    m_inputBufferSize = p.inputBufferSize;\n    m_inputWriteOffset = p.inputBufferSize;\n    m_audioBufferSize = p.audioBufferSize;\n    m_inputSampleRate = p.inputSampleRate;\n    m_audioSampleRate = p.audioSampleRate;\n    m_audioParameters = p.initialAudioParameters;\n\n    m_inputSamplesRead = 0;\n\n    m_inputWriteOffset = 0;\n    m_processed = true;\n\n    m_audioBuffer.initialize(p.audioBufferSize);\n    m_inputChannels = new InputChannel[p.inputChannelCount];\n    for (int i = 0; i < p.inputChannelCount; ++i) {\n        m_inputChannels[i].transferBuffer = new float[p.inputBufferSize];\n        m_inputChannels[i].data.initialize(p.inputBufferSize);\n    }\n\n    m_filters = new ProcessingFilters[p.inputChannelCount];\n    for (int i = 0; i < p.inputChannelCount; ++i) {\n        m_filters[i].airNoiseLowPass.setCutoffFrequency(\n            m_audioParameters.airNoiseFrequencyCutoff, m_audioSampleRate);\n\n        m_filters[i].derivative.m_dt = 1 / m_audioSampleRate;\n\n        m_filters[i].inputDcFilter.setCutoffFrequency(10.0);\n        m_filters[i].inputDcFilter.m_dt = 1 / m_audioSampleRate;\n\n        m_filters[i].jitterFilter.initialize(\n            10,\n            m_audioParameters.inputSampleNoiseFrequencyCutoff,\n            m_audioSampleRate);\n\n        m_filters[i].antialiasing.setCutoffFrequency(1900.0f, m_audioSampleRate);\n    }\n\n    m_levelingFilter.p_target = m_audioParameters.levelerTarget;\n    m_levelingFilter.p_maxLevel = m_audioParameters.levelerMaxGain;\n    m_levelingFilter.p_minLevel = m_audioParameters.levelerMinGain;\n    m_antialiasing.setCutoffFrequency(m_audioSampleRate * 0.45f, m_audioSampleRate);\n\n    for (int i = 0; i < m_audioBufferSize; ++i) {\n        m_audioBuffer.write(0);\n    }\n}\n\nvoid Synthesizer::initializeImpulseResponse(\n    const int16_t *impulseResponse,\n    unsigned int samples,\n    float volume,\n    int index)\n{\n    unsigned int clippedLength = 0;\n    for (unsigned int i = 0; i < samples; ++i) {\n        if (std::abs(impulseResponse[i]) > 100) {\n            clippedLength = i + 1;\n        }\n    }\n\n    const unsigned int sampleCount = std::min(10000U, clippedLength);\n    m_filters[index].convolution.initialize(sampleCount);\n    for (unsigned int i = 0; i < sampleCount; ++i) {\n        m_filters[index].convolution.getImpulseResponse()[i] =\n            volume * impulseResponse[i] / INT16_MAX;\n    }\n}\n\nvoid Synthesizer::startAudioRenderingThread() {\n    m_run = true;\n    m_thread = new std::thread(&Synthesizer::audioRenderingThread, this);\n}\n\nvoid Synthesizer::endAudioRenderingThread() {\n    if (m_thread != nullptr) {\n        m_run = false;\n        endInputBlock();\n\n        m_thread->join();\n        delete m_thread;\n\n        m_thread = nullptr;\n    }\n}\n\nvoid Synthesizer::destroy() {\n    m_audioBuffer.destroy();\n\n    for (int i = 0; i < m_inputChannelCount; ++i) {\n        m_inputChannels[i].data.destroy();\n        m_filters[i].convolution.destroy();\n    }\n\n    delete[] m_inputChannels;\n    delete[] m_filters;\n\n    m_inputChannels = nullptr;\n    m_filters = nullptr;\n\n    m_inputChannelCount = 0;\n}\n\nint Synthesizer::readAudioOutput(int samples, int16_t *buffer) {\n    std::lock_guard<std::mutex> lock(m_lock0);\n\n    const int newDataLength = m_audioBuffer.size();\n    if (newDataLength >= samples) {\n        m_audioBuffer.readAndRemove(samples, buffer);\n    }\n    else {\n        m_audioBuffer.readAndRemove(newDataLength, buffer);\n        memset(\n            buffer + newDataLength,\n            0,\n            sizeof(int16_t) * ((size_t)samples - newDataLength));\n    }\n    \n    const int samplesConsumed = std::min(samples, newDataLength);\n\n    return samplesConsumed;\n}\n\nvoid Synthesizer::waitProcessed() {\n    {\n        std::unique_lock<std::mutex> lk(m_lock0);\n        m_cv0.wait(lk, [this] { return m_processed; });\n    }\n}\n\nvoid Synthesizer::writeInput(const double *data) {\n    m_inputWriteOffset += (double)m_audioSampleRate / m_inputSampleRate;\n    if (m_inputWriteOffset >= (double)m_inputBufferSize) {\n        m_inputWriteOffset -= (double)m_inputBufferSize;\n    }\n\n    for (int i = 0; i < m_inputChannelCount; ++i) {\n        RingBuffer<float> &buffer = m_inputChannels[i].data;\n        const double lastInputSample = m_inputChannels[i].lastInputSample;\n        const size_t baseIndex = buffer.writeIndex();\n        const double distance =\n            inputDistance(m_inputWriteOffset, m_lastInputSampleOffset);\n        double s =\n            inputDistance(baseIndex, m_lastInputSampleOffset);\n        for (; s <= distance; s += 1.0) {\n            if (s >= m_inputBufferSize) s -= m_inputBufferSize;\n\n            const double f = s / distance;\n            const double sample = lastInputSample * (1 - f) + data[i] * f;\n\n            buffer.write(m_filters[i].antialiasing.fast_f(static_cast<float>(sample)));\n        }\n\n        m_inputChannels[i].lastInputSample = data[i];\n    }\n\n    m_lastInputSampleOffset = m_inputWriteOffset;\n}\n\nvoid Synthesizer::endInputBlock() {\n    std::unique_lock<std::mutex> lk(m_inputLock); \n\n    for (int i = 0; i < m_inputChannelCount; ++i) {\n        m_inputChannels[i].data.removeBeginning(m_inputSamplesRead);\n    }\n\n    if (m_inputChannelCount != 0) {\n        m_latency = m_inputChannels[0].data.size();\n    }\n    \n    m_inputSamplesRead = 0;\n    m_processed = false;\n\n    lk.unlock();\n    m_cv0.notify_one();\n}\n\nvoid Synthesizer::audioRenderingThread() {\n    while (m_run) {\n        renderAudio();\n    }\n}\n\n#undef max\nvoid Synthesizer::renderAudio() {\n    std::unique_lock<std::mutex> lk0(m_lock0);\n\n    m_cv0.wait(lk0, [this] {\n        const bool inputAvailable =\n            m_inputChannels[0].data.size() > 0\n            && m_audioBuffer.size() < 2000;\n        return !m_run || (inputAvailable && !m_processed);\n    });\n\n    const int n = std::min(\n        std::max(0, 2000 - (int)m_audioBuffer.size()),\n        (int)m_inputChannels[0].data.size());\n\n    for (int i = 0; i < m_inputChannelCount; ++i) {\n        m_inputChannels[i].data.read(n, m_inputChannels[i].transferBuffer);\n    }\n    \n    m_inputSamplesRead = n;\n    m_processed = true;\n\n    lk0.unlock();\n\n    for (int i = 0; i < m_inputChannelCount; ++i) {\n        m_filters[i].airNoiseLowPass.setCutoffFrequency(\n            static_cast<float>(m_audioParameters.airNoiseFrequencyCutoff), m_audioSampleRate);\n        m_filters[i].jitterFilter.setJitterScale(m_audioParameters.inputSampleNoise);\n    }\n\n    for (int i = 0; i < n; ++i) {\n        m_audioBuffer.write(renderAudio(i));\n    }\n\n    m_cv0.notify_one();\n}\n\ndouble Synthesizer::getLatency() const {\n    return (double)m_latency / m_audioSampleRate;\n}\n\nint Synthesizer::inputDelta(int s1, int s0) const {\n    return (s1 < s0)\n        ? m_inputBufferSize - s0 + s1\n        : s1 - s0;\n}\n\ndouble Synthesizer::inputDistance(double s1, double s0) const {\n    return (s1 < s0)\n        ? (double)m_inputBufferSize - s0 + s1\n        : s1 - s0;\n}\n\nvoid Synthesizer::setInputSampleRate(double sampleRate) {\n    if (sampleRate != m_inputSampleRate) {\n        std::lock_guard<std::mutex> lock(m_lock0);\n        m_inputSampleRate = sampleRate;\n    }\n}\n\nint16_t Synthesizer::renderAudio(int inputSample) {\n    const float airNoise = m_audioParameters.airNoise;\n    const float dF_F_mix = m_audioParameters.dF_F_mix;\n    const float convAmount = m_audioParameters.convolution;\n\n    float signal = 0;\n    for (int i = 0; i < m_inputChannelCount; ++i) {\n        const float r_0 = 2.0 * ((double)rand() / RAND_MAX) - 1.0;\n\n        const float jitteredSample =\n            m_filters[i].jitterFilter.fast_f(m_inputChannels[i].transferBuffer[inputSample]);\n\n        const float f_in = jitteredSample;\n        const float f_dc = m_filters[i].inputDcFilter.fast_f(f_in);\n        const float f = f_in - f_dc;\n        const float f_p = m_filters[i].derivative.f(f_in);\n\n        const float noise = 2.0 * ((double)rand() / RAND_MAX) - 1.0;\n        const float r =\n            m_filters->airNoiseLowPass.fast_f(noise);\n        const float r_mixed =\n            airNoise * r + (1 - airNoise);\n\n        float v_in =\n            f_p * dF_F_mix\n            + f * r_mixed * (1 - dF_F_mix);\n        if (fpclassify(v_in) == FP_SUBNORMAL) {\n            v_in = 0;\n        }\n\n        const float v =\n            convAmount * m_filters[i].convolution.f(v_in)\n            + (1 - convAmount) * v_in;\n\n        signal += v;\n    }\n\n    signal = m_antialiasing.fast_f(signal);\n\n    m_levelingFilter.p_target = m_audioParameters.levelerTarget;\n    const float v_leveled = m_levelingFilter.f(signal) * m_audioParameters.volume;\n    int r_int = std::lround(v_leveled);\n    if (r_int > INT16_MAX) {\n        r_int = INT16_MAX;\n    }\n    else if (r_int < INT16_MIN) {\n        r_int = INT16_MIN;\n    }\n\n    return static_cast<int16_t>(r_int);\n}\n\ndouble Synthesizer::getLevelerGain() {\n    std::lock_guard<std::mutex> lock(m_lock0);\n    return m_levelingFilter.getAttenuation();\n}\n\nSynthesizer::AudioParameters Synthesizer::getAudioParameters() {\n    std::lock_guard<std::mutex> lock(m_lock0);\n    return m_audioParameters;\n}\n\nvoid Synthesizer::setAudioParameters(const AudioParameters &params) {\n    std::lock_guard<std::mutex> lock(m_lock0);\n    m_audioParameters = params;\n}\n"
  },
  {
    "path": "src/throttle.cpp",
    "content": "#include \"../include/throttle.h\"\n\nThrottle::Throttle() {\n    m_speedControl = 0;\n}\n\nThrottle::~Throttle() {\n    /* void */\n}\n\nvoid Throttle::setSpeedControl(double s) {\n    m_speedControl = s;\n}\n\nvoid Throttle::update(double dt, Engine *engine) {\n    /* void */\n}\n"
  },
  {
    "path": "src/throttle_display.cpp",
    "content": "#include \"../include/throttle_display.h\"\n\n#include \"../include/geometry_generator.h\"\n#include \"../include/engine_sim_application.h\"\n\n#include \"../include/ui_utilities.h\"\n\nThrottleDisplay::ThrottleDisplay() {\n    m_engine = nullptr;\n}\n\nThrottleDisplay::~ThrottleDisplay() {\n    /* void */\n}\n\nvoid ThrottleDisplay::initialize(EngineSimApplication *app) {\n    UiElement::initialize(app);\n}\n\nvoid ThrottleDisplay::destroy() {\n    UiElement::destroy();\n}\n\nvoid ThrottleDisplay::update(float dt) {\n    UiElement::update(dt);\n}\n\nvoid ThrottleDisplay::render() {\n    UiElement::render();\n\n    drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor());\n\n    const Bounds bounds = m_bounds.inset(10.0f);\n    const Bounds title = bounds.verticalSplit(1.0f, 0.9f);\n    drawCenteredText(\"THROTTLE\", title.inset(10.0f), 24.0f);\n\n    const Bounds mainDrawArea = bounds.verticalSplit(0.05f, 0.9f);\n    renderThrottle(mainDrawArea);\n\n    const Bounds speedControlDrawArea = bounds.verticalSplit(0.0f, 0.05f);\n    renderSpeedControl(speedControlDrawArea);\n}\n\nvoid ThrottleDisplay::renderThrottle(const Bounds &bounds) {\n    GeometryGenerator *gen = m_app->getGeometryGenerator();\n\n    const float width = pixelsToUnits(bounds.width());\n    const float height = pixelsToUnits(bounds.height());\n    const float size = std::fmin(width, height);\n\n    const Point origin = getRenderPoint(bounds.getPosition(Bounds::center));\n\n    const float carbBore = size * 0.4f;\n    const float carbHeight = size * 0.5f;\n    const float plateWidth = carbBore * 0.8f;\n\n    GeometryGenerator::Line2dParameters params;\n    params.lineWidth = size * 0.01f;\n\n    GeometryGenerator::Circle2dParameters circleParams;\n    circleParams.radius = params.lineWidth / 2.0f;\n    circleParams.maxEdgeLength = m_app->pixelsToUnits(5.0f);\n\n    GeometryGenerator::GeometryIndices main, pivot, pivotShadow;\n    gen->startShape();\n\n    params.x0 = origin.x + carbBore / 2.0f;\n    params.y0 = origin.y - carbHeight / 2.0f;\n    params.x1 = origin.x + carbBore / 2.0f;\n    params.y1 = origin.y + carbHeight / 2.0f;\n    gen->generateLine2d(params);\n\n    circleParams.center_x = params.x1;\n    circleParams.center_y = params.y1;\n    gen->generateCircle2d(circleParams);\n\n    circleParams.center_x = params.x1;\n    circleParams.center_y = params.y0;\n    gen->generateCircle2d(circleParams);\n\n    params.x0 = origin.x - carbBore / 2.0f;\n    params.x1 = origin.x - carbBore / 2.0f;\n    gen->generateLine2d(params);\n\n    circleParams.center_x = params.x1;\n    circleParams.center_y = params.y1;\n    gen->generateCircle2d(circleParams);\n\n    circleParams.center_x = params.x1;\n    circleParams.center_y = params.y0;\n    gen->generateCircle2d(circleParams);\n\n    // Draw throttle plate\n    const float throttleAngle = (m_engine == nullptr)\n        ? 0.0f\n        : static_cast<float>(m_engine->getThrottlePlateAngle());\n    const float cos_theta = std::cosf(throttleAngle);\n    const float sin_theta = std::sinf(throttleAngle);\n\n    params.y0 = origin.y - sin_theta * plateWidth / 2.0f;\n    params.x0 = origin.x - cos_theta * plateWidth / 2.0f;\n    params.y1 = origin.y + sin_theta * plateWidth / 2.0f;\n    params.x1 = origin.x + cos_theta * plateWidth / 2.0f;\n    gen->generateLine2d(params);\n\n    circleParams.center_x = params.x0;\n    circleParams.center_y = params.y0;\n    gen->generateCircle2d(circleParams);\n\n    circleParams.center_x = params.x1;\n    circleParams.center_y = params.y1;\n    gen->generateCircle2d(circleParams);\n\n    gen->endShape(&main);\n\n    gen->startShape();\n    circleParams.center_x = origin.x;\n    circleParams.center_y = origin.y;\n    circleParams.radius = size * 0.01f;\n    gen->generateCircle2d(circleParams);\n    gen->endShape(&pivot);\n\n    gen->startShape();\n    circleParams.center_x = origin.x;\n    circleParams.center_y = origin.y;\n    circleParams.radius = 2 * size * 0.01f;\n    gen->generateCircle2d(circleParams);\n    gen->endShape(&pivotShadow);\n\n    m_app->getShaders()->SetBaseColor(m_app->getForegroundColor());\n    m_app->drawGenerated(main, 0x11, m_app->getShaders()->GetUiFlags());\n\n    m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor());\n    m_app->drawGenerated(pivotShadow, 0x11, m_app->getShaders()->GetUiFlags());\n\n    m_app->getShaders()->SetBaseColor(m_app->getForegroundColor());\n    m_app->drawGenerated(pivot, 0x11, m_app->getShaders()->GetUiFlags());\n}\n\nvoid ThrottleDisplay::renderSpeedControl(const Bounds &bounds) {\n    GeometryGenerator *gen = m_app->getGeometryGenerator();\n\n    Grid grid;\n    grid.v_cells = 1;\n    grid.h_cells = 3;\n    const Bounds b = grid.get(bounds, 1, 0);\n\n    const Point lm = b.getPosition(Bounds::lm);\n    const Point rm = b.getPosition(Bounds::rm);\n\n    const float s = (m_engine != nullptr)\n        ? static_cast<float>(m_engine->getSpeedControl())\n        : 0;\n    const Bounds bar = Bounds(b.width(), 2.0f, lm, Bounds::lm);\n    const Bounds speedControlBar = Bounds(b.width() * s, 2.0f, lm, Bounds::lm);\n\n    drawFrame(\n        bar,\n        1.0f,\n        mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.001f),\n        mix(m_app->getBackgroundColor(), m_app->getRed(), 0.01f)\n    );\n\n    drawBox(\n        speedControlBar,\n        m_app->getRed()\n    );\n}\n"
  },
  {
    "path": "src/transmission.cpp",
    "content": "#include \"../include/transmission.h\"\n\n#include \"../include/units.h\"\n\n#include <cmath>\n\nTransmission::Transmission() {\n    m_gear = -1;\n    m_newGear = -1;\n    m_gearCount = 0;\n    m_gearRatios = nullptr;\n    m_maxClutchTorque = units::torque(1000.0, units::ft_lb);\n    m_rotatingMass = nullptr;\n    m_vehicle = nullptr;\n    m_clutchPressure = 0.0;\n}\n\nTransmission::~Transmission() {\n    if (m_gearRatios != nullptr) {\n        delete[] m_gearRatios;\n    }\n\n    m_gearRatios = nullptr;\n}\n\nvoid Transmission::initialize(const Parameters &params) {\n    m_gearCount = params.GearCount;\n    m_maxClutchTorque = params.MaxClutchTorque;\n    m_gearRatios = new double[params.GearCount];\n    memcpy(m_gearRatios, params.GearRatios, sizeof(double) * m_gearCount);\n}\n\nvoid Transmission::update(double dt) {\n    if (m_gear == -1) {\n        m_clutchConstraint.m_minTorque = 0;\n        m_clutchConstraint.m_maxTorque = 0;\n    }\n    else {\n        m_clutchConstraint.m_minTorque = -m_maxClutchTorque * m_clutchPressure;\n        m_clutchConstraint.m_maxTorque = m_maxClutchTorque * m_clutchPressure;\n    }\n}\n\nvoid Transmission::addToSystem(\n    atg_scs::RigidBodySystem *system,\n    atg_scs::RigidBody *rotatingMass,\n    Vehicle *vehicle,\n    Engine *engine)\n{\n    m_rotatingMass = rotatingMass;\n    m_vehicle = vehicle;\n\n    m_clutchConstraint.setBody1(&engine->getOutputCrankshaft()->m_body);\n    m_clutchConstraint.setBody2(m_rotatingMass);\n\n    system->addConstraint(&m_clutchConstraint);\n}\n\nvoid Transmission::changeGear(int newGear) {\n    if (newGear < -1 || newGear >= m_gearCount) return;\n    else if (newGear != -1) {\n        const double m_car = m_vehicle->getMass();\n        const double gear_ratio = m_gearRatios[newGear];\n        const double diff_ratio = m_vehicle->getDiffRatio();\n        const double tire_radius = m_vehicle->getTireRadius();\n        const double f = tire_radius / (diff_ratio * gear_ratio);\n\n        const double new_I = m_car * f * f;\n        const double E_r =\n            0.5 * m_rotatingMass->I * m_rotatingMass->v_theta * m_rotatingMass->v_theta;\n        const double new_v_theta = m_rotatingMass->v_theta < 0\n            ? -std::sqrt(E_r * 2 / new_I)\n            : std::sqrt(E_r * 2 / new_I);\n\n        m_rotatingMass->I = new_I;\n        m_rotatingMass->p_x = m_rotatingMass->p_y = 0;\n        m_rotatingMass->m = m_car;\n        m_rotatingMass->v_theta = new_v_theta;\n    }\n\n    m_gear = newGear;\n}\n"
  },
  {
    "path": "src/ui_button.cpp",
    "content": "#include \"../include/ui_button.h\"\n\n#include \"../include/engine_sim_application.h\"\n#include \"../include/ui_utilities.h\"\n\nUiButton::UiButton() {\n    m_text = \"\";\n    m_fontSize = 12;\n    m_checkMouse = true;\n}\n\nUiButton::~UiButton() {\n    /* void */\n}\n\nvoid UiButton::update(float dt) {\n    m_mouseBounds = m_bounds;\n}\n\nvoid UiButton::render() {\n    ysVector color = m_app->getBackgroundColor();\n    if (isMouseHeld()) {\n        color = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.02f);\n    }\n    else if (isMouseOver()) {\n        color = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.01f);\n    }\n\n    drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), color);\n    drawCenteredText(m_text, m_bounds, m_fontSize);\n}\n"
  },
  {
    "path": "src/ui_element.cpp",
    "content": "#include \"../include/ui_element.h\"\n\n#include \"../include/engine_sim_application.h\"\n\n#include <assert.h>\n\nUiElement::UiElement() {\n    m_app = nullptr;\n    m_parent = nullptr;\n    m_signalTarget = nullptr;\n    m_checkMouse = false;\n    m_disabled = false;\n    m_index = -1;\n\n    m_draggable = false;\n    m_mouseOver = false;\n    m_mouseHeld = false;\n    m_visible = true;\n}\n\nUiElement::~UiElement() {\n    /* void */\n}\n\nvoid UiElement::initialize(EngineSimApplication *app) {\n    m_app = app;\n}\n\nvoid UiElement::destroy() {\n    for (UiElement *child : m_children) {\n        child->destroy();\n        delete child;\n    }\n\n    m_children.clear();\n}\n\nvoid UiElement::update(float dt) {\n    for (UiElement *child : m_children) {\n        child->update(dt);\n    }\n}\n\nvoid UiElement::render() {\n    for (UiElement *child : m_children) {\n        if (child->isVisible()) child->render();\n    }\n}\n\nvoid UiElement::signal(UiElement *element, Event event) {\n    /* void */\n}\n\nvoid UiElement::onMouseDown(const Point &mouseLocal) {\n    m_mouseHeld = true;\n}\n\nvoid UiElement::onMouseUp(const Point &mouseLocal) {\n    m_mouseHeld = false;\n}\n\nvoid UiElement::onMouseClick(const Point &mouseLocal) {\n    signal(Event::Clicked);\n}\n\nvoid UiElement::onDrag(const Point &p0, const Point &mouse0, const Point &mouse) {\n    if (m_draggable) {\n        m_localPosition = p0 + (mouse - mouse0);\n    }\n}\n\nvoid UiElement::onMouseOver(const Point &mouseLocal) {\n    m_mouseOver = true;\n}\n\nvoid UiElement::onMouseLeave() {\n    m_mouseOver = false;\n}\n\nvoid UiElement::onMouseScroll(int mouseScroll) {\n    /* void */\n}\n\nUiElement *UiElement::mouseOver(const Point &mouseLocal) {\n    if (m_disabled) return nullptr;\n\n    const int n = (int)getChildCount();\n    for (int i = n - 1; i >= 0; --i) {\n        UiElement *child = m_children[i];\n        UiElement *clickedElement = child->mouseOver(mouseLocal - child->m_localPosition);\n        if (clickedElement != nullptr) {\n            return clickedElement;\n        }\n    }\n\n    return (m_checkMouse && m_mouseBounds.overlaps(mouseLocal))\n        ? this\n        : nullptr;\n}\n\nPoint UiElement::getWorldPosition() const {\n    return (m_parent != nullptr)\n        ? m_parent->getWorldPosition() + m_localPosition\n        : m_localPosition;\n}\n\nvoid UiElement::setLocalPosition(const Point &p, const Point &ref) {\n    const Point current = m_bounds.getPosition(ref) + m_localPosition;\n    m_localPosition += (p - current);\n}\n\nvoid UiElement::bringToFront(UiElement *element) {\n    assert(element->m_parent == this);\n\n    m_children.erase(m_children.begin() + element->m_index);\n    m_children.push_back(element);\n\n    int i = 0;\n    for (UiElement *element : m_children) {\n        element->m_index = i++;\n    }\n}\n\nvoid UiElement::activate() {\n    if (m_parent != nullptr) {\n        m_parent->bringToFront(this);\n        m_parent->activate();\n    }\n}\n\nvoid UiElement::signal(Event event) {\n    if (m_signalTarget == nullptr) return;\n\n    m_signalTarget->signal(this, event);\n}\n\nfloat UiElement::pixelsToUnits(float length) const {\n    return length;// m_app->pixelsToUnits(length);\n}\n\nPoint UiElement::pixelsToUnits(const Point &p) const {\n    return { pixelsToUnits(p.x), pixelsToUnits(p.y) };\n}\n\nfloat UiElement::unitsToPixels(float x) const {\n    return x; // m_app->unitsToPixels(x);\n}\n\nPoint UiElement::unitsToPixels(const Point &p) const {\n    return { unitsToPixels(p.x), unitsToPixels(p.y) };\n}\n\nPoint UiElement::getRenderPoint(const Point &p) const {\n    const Point offset(\n            -(float)m_app->getScreenWidth() / 2,\n            -(float)m_app->getScreenHeight() / 2);\n    const Point posPixels = localToWorld(p) + offset;\n\n    return pixelsToUnits(posPixels);\n}\n\nBounds UiElement::getRenderBounds(const Bounds &b) const {\n    return { getRenderPoint(b.m0), getRenderPoint(b.m1) };\n}\n\nBounds UiElement::unitsToPixels(const Bounds &b) const {\n    return { unitsToPixels(b.m0), unitsToPixels(b.m1) };\n}\n\nvoid UiElement::resetShader() {\n    m_app->getShaders()->ResetBaseColor();\n    m_app->getShaders()->SetObjectTransform(ysMath::LoadIdentity());\n}\n\nvoid UiElement::drawModel(\n    dbasic::ModelAsset *model,\n    const ysVector &color,\n    const Point &p,\n    const Point &s)\n{\n    resetShader();\n\n    const Point p_render = getRenderPoint(p);\n    const Point s_render = pixelsToUnits(s);\n\n    m_app->getShaders()->SetObjectTransform(\n        ysMath::MatMult(\n            ysMath::TranslationTransform(ysMath::LoadVector(p_render.x, p_render.y, 0.0)),\n            ysMath::ScaleTransform(ysMath::LoadVector(s_render.x, s_render.y, 0.0))\n        )\n    );\n\n    m_app->getShaders()->SetBaseColor(color);\n    m_app->getEngine()->DrawModel(m_app->getShaders()->GetUiFlags(), model, 0x11);\n}\n\nvoid UiElement::drawFrame(\n        const Bounds &bounds,\n        float thickness,\n        const ysVector &frameColor,\n        const ysVector &fillColor,\n        bool fill)\n{\n    GeometryGenerator *generator = m_app->getGeometryGenerator();\n\n    const Bounds worldBounds = getRenderBounds(bounds);\n    const Point position = worldBounds.getPosition(Bounds::center);\n\n    GeometryGenerator::FrameParameters params;\n    params.frameWidth = worldBounds.width();\n    params.frameHeight = worldBounds.height();\n    params.lineWidth = pixelsToUnits(thickness);\n    params.x = position.x;\n    params.y = position.y;\n\n    GeometryGenerator::Line2dParameters lineParams;\n    lineParams.lineWidth = worldBounds.height();\n    lineParams.y0 = lineParams.y1 = worldBounds.getPosition(Bounds::center).y;\n    lineParams.x0 = worldBounds.left();\n    lineParams.x1 = worldBounds.right();\n\n    GeometryGenerator::GeometryIndices frame, body;\n\n    resetShader();\n    if (fill) {\n        generator->startShape();\n        generator->generateLine2d(lineParams);\n        generator->endShape(&body);\n\n        m_app->getShaders()->SetBaseColor(fillColor);\n        m_app->drawGenerated(body, 0x11, m_app->getShaders()->GetUiFlags());\n    }\n\n    generator->startShape();\n    generator->generateFrame(params);\n    generator->endShape(&frame);\n\n    m_app->getShaders()->SetBaseColor(frameColor);\n    m_app->drawGenerated(frame, 0x11, m_app->getShaders()->GetUiFlags());\n}\n\nvoid UiElement::drawBox(const Bounds &bounds, const ysVector &fillColor) {\n    GeometryGenerator *generator = m_app->getGeometryGenerator();\n    const Bounds worldBounds = getRenderBounds(bounds);\n\n    GeometryGenerator::Line2dParameters lineParams;\n    lineParams.lineWidth = worldBounds.height();\n    lineParams.y0 = lineParams.y1 = worldBounds.getPosition(Bounds::center).y;\n    lineParams.x0 = worldBounds.left();\n    lineParams.x1 = worldBounds.right();\n\n    GeometryGenerator::GeometryIndices body;\n    generator->startShape();\n    generator->generateLine2d(lineParams);\n    generator->endShape(&body);\n\n    resetShader();\n    m_app->getShaders()->SetBaseColor(fillColor);\n    m_app->drawGenerated(body, 0x11, m_app->getShaders()->GetUiFlags());\n}\n\nvoid UiElement::drawText(\n        const std::string &s,\n        const Bounds &bounds,\n        float height,\n        const Point &ref)\n{\n    const Bounds renderBounds = unitsToPixels(getRenderBounds(bounds));\n    const Point origin = renderBounds.getPosition(ref);\n\n    m_app->getTextRenderer()->RenderText(\n            s, origin.x, origin.y - height / 4, height);\n}\n\nvoid UiElement::drawAlignedText(\n        const std::string &s,\n        const Bounds &bounds,\n        float height,\n        const Point &ref,\n        const Point &refText)\n{\n    const Bounds renderBounds = unitsToPixels(getRenderBounds(bounds));\n    const Point origin = renderBounds.getPosition(ref);\n\n    const float textWidth = m_app->getTextRenderer()->CalculateWidth(s, height);\n    const float textHeight = height;\n\n    const Bounds textBounds(\n        textWidth,\n        textHeight,\n        { 0.0f, textHeight - textHeight * 0.25f },\n        Bounds::tl);\n    const Point r = textBounds.getPosition(refText);\n\n    m_app->getTextRenderer()->RenderText(\n        s, origin.x - r.x, origin.y - r.y, height);\n}\n\nvoid UiElement::drawCenteredText(\n        const std::string &s,\n        const Bounds &bounds,\n        float height,\n        const Point &ref)\n{\n    const Bounds renderBounds = unitsToPixels(getRenderBounds(bounds));\n    const Point origin = renderBounds.getPosition(ref);\n\n    const float width = m_app->getTextRenderer()->CalculateWidth(s, height);\n    m_app->getTextRenderer()->RenderText(\n            s, origin.x - width / 2, origin.y - height / 4, height);\n}\n"
  },
  {
    "path": "src/ui_manager.cpp",
    "content": "#include \"../include/ui_manager.h\"\n\n#include \"../include/engine_sim_application.h\"\n\nUiManager::UiManager() {\n    m_app = nullptr;\n    m_dragStart = nullptr;\n    m_hover = nullptr;\n    m_lastMouseScroll = 0;\n}\n\nUiManager::~UiManager() {\n    /* void */\n}\n\nvoid UiManager::initialize(EngineSimApplication *app) {\n    m_app = app;\n    m_root.initialize(app);\n}\n\nvoid UiManager::destroy() {\n    m_root.destroy();\n    m_hover = nullptr;\n    m_dragStart = nullptr;\n    m_app = nullptr;\n}\n\nvoid UiManager::update(float dt) {\n    m_root.update(dt);\n\n    int mouse_x, mouse_y;\n    m_app->getEngine()->GetOsMousePos(&mouse_x, &mouse_y);\n\n    Point mousePos = { (float)mouse_x, (float)mouse_y };\n    UiElement *newHover = m_root.mouseOver(mousePos);\n    if (newHover != m_hover) {\n        if (m_hover != nullptr) m_hover->onMouseLeave();\n        if (newHover != nullptr) newHover->onMouseOver(mousePos);\n        m_hover = newHover;\n    }\n\n    if (m_app->getEngine()->ProcessMouseButtonDown(ysMouse::Button::Left)) {\n        m_dragStart = m_hover;\n        m_mouse_p0 = mousePos;\n        if (m_dragStart != nullptr) {\n            m_drag_p0 = m_dragStart->getLocalPosition();\n            m_dragStart->onMouseDown(m_dragStart->worldToLocal(mousePos));\n        }\n    }\n    else if (m_app->getEngine()->ProcessMouseButtonUp(ysMouse::Button::Left)) {\n        UiElement *dragRelease = m_hover;\n\n        if (m_dragStart != nullptr) m_dragStart->onMouseUp(mousePos);\n\n        if (dragRelease != nullptr && m_dragStart == dragRelease) {\n            m_dragStart->onMouseClick(m_dragStart->worldToLocal(mousePos));\n        }\n\n        m_dragStart = nullptr;\n    }\n\n    const int newMouseScroll = m_app->getEngine()->GetMouseWheel();\n    if (m_lastMouseScroll != newMouseScroll) {\n        if (m_hover != nullptr) {\n            m_hover->onMouseScroll(newMouseScroll - m_lastMouseScroll);\n        }\n\n        m_lastMouseScroll = newMouseScroll;\n    }\n\n    if (m_dragStart != nullptr) {\n        m_dragStart->onDrag(m_drag_p0, m_mouse_p0, mousePos);\n    }\n}\n\nvoid UiManager::render() {\n    m_root.render();\n}\n"
  },
  {
    "path": "src/ui_math.cpp",
    "content": "#include \"../include/ui_math.h\"\n\nconst Point Bounds::center = { 0.5f, 0.5f };\nconst Point Bounds::tl = { 0.0f, 1.0f };\nconst Point Bounds::tr = { 1.0f, 0.0f };\nconst Point Bounds::tm = { 0.5f, 1.0f };\nconst Point Bounds::bl = { 0.0f, 0.0f };\nconst Point Bounds::br = { 1.0f, 0.0f };\nconst Point Bounds::bm = { 0.5f, 0.0f };\nconst Point Bounds::lm = { 0.0f, 0.5f };\nconst Point Bounds::rm = { 1.0f, 0.5f };\n"
  },
  {
    "path": "src/ui_utilities.cpp",
    "content": "#include \"../include/ui_utilities.h\"\n\nysVector mix(const ysVector &c1, const ysVector &c2, float s) {\n    return ysMath::Add(\n            ysMath::Mul(c1, ysMath::LoadScalar(1 - s)),\n            ysMath::Mul(c2, ysMath::LoadScalar(s)));\n}\n"
  },
  {
    "path": "src/utilities.cpp",
    "content": "#include \"../include/utilities.h\"\n\n#include <cmath>\n\ndouble modularDistance(double a0, double b0, double mod) {\n    double a, b;\n    if (a0 < b0) {\n        a = a0;\n        b = b0;\n    }\n    else {\n        a = b0;\n        b = a0;\n    }\n\n    return std::fmin(b - a, a + mod - b);\n}\n\ndouble positiveMod(double x, double mod) {\n    if (x < 0) {\n        x = std::ceil(-x / mod) * mod + x;\n    }\n\n    return std::fmod(x, mod);\n}\n\ndouble erfApproximation(double x) {\n    const double a1 = 0.278393;\n    const double a2 = 0.230389;\n    const double a3 = 0.000972;\n    const double a4 = 0.078108;\n\n    const double x2 = x * x;\n    const double x3 = x2 * x;\n    const double x4 = x3 * x;\n\n    const double q = 1 / (1 + a1 * x + a2 * x2 + a3 * x3 + a4 * x4);\n    const double q2 = q * q;\n    const double q4 = q2 * q2;\n\n    return 1 - q4;\n}\n"
  },
  {
    "path": "src/valvetrain.cpp",
    "content": "#include \"../include/valvetrain.h\"\n\nValvetrain::Valvetrain() {\n    /* void */\n}\n\nValvetrain::~Valvetrain() {\n    /* void */\n}\n"
  },
  {
    "path": "src/vehicle.cpp",
    "content": "#include \"../include/vehicle.h\"\n\n#include <cmath>\n\nVehicle::Vehicle() {\n    m_rotatingMass = nullptr;\n    m_mass = 0;\n    m_dragCoefficient = 0;\n    m_crossSectionArea = 0;\n    m_diffRatio = 0;\n    m_tireRadius = 0;\n    m_travelledDistance = 0;\n    m_rollingResistance = 0;\n}\n\nVehicle::~Vehicle() {\n    /* void */\n}\n\nvoid Vehicle::initialize(const Parameters &params) {\n    m_mass = params.mass;\n    m_dragCoefficient = params.dragCoefficient;\n    m_crossSectionArea = params.crossSectionArea;\n    m_diffRatio = params.diffRatio;\n    m_tireRadius = params.tireRadius;\n    m_rollingResistance = params.rollingResistance;\n}\n\nvoid Vehicle::update(double dt) {\n    m_travelledDistance += getSpeed() * dt;\n}\n\nvoid Vehicle::addToSystem(atg_scs::RigidBodySystem *system, atg_scs::RigidBody *rotatingMass) {\n    m_rotatingMass = rotatingMass;\n}\n\ndouble Vehicle::getSpeed() const {\n    const double E_r = 0.5 * m_rotatingMass->I * m_rotatingMass->v_theta * m_rotatingMass->v_theta;\n    const double vehicleSpeed = std::sqrt(2 * E_r / m_mass);\n\n    return vehicleSpeed;\n\n    // E_r = 0.5 * I * v_theta^2\n    // E_k = 0.5 * m * v^2\n}\n\ndouble Vehicle::linearForceToVirtualTorque(double force) const {\n    const double rotationToKineticRatio =\n        std::sqrt(m_rotatingMass->I / m_mass);\n    return rotationToKineticRatio * force;\n}\n"
  },
  {
    "path": "src/vehicle_drag_constraint.cpp",
    "content": "#include \"../include/vehicle_drag_constraint.h\"\n\n#include \"../include/constants.h\"\n#include \"../include/units.h\"\n#include \"../include/vehicle.h\"\n\nVehicleDragConstraint::VehicleDragConstraint() : Constraint(1, 1) {\n    m_ks = 10.0;\n    m_kd = 1.0;\n\n    m_vehicle = nullptr;\n}\n\nVehicleDragConstraint::~VehicleDragConstraint() {\n    /* void */\n}\n\nvoid VehicleDragConstraint::initialize(atg_scs::RigidBody *rotatingMass, Vehicle *vehicle) {\n    m_bodies[0] = rotatingMass;\n    m_vehicle = vehicle;\n}\n\nvoid VehicleDragConstraint::calculate(Output *output, atg_scs::SystemState *system) {\n    output->C[0] = 0;\n\n    output->J[0][0] = 0.0;\n    output->J[0][1] = 0.0;\n    output->J[0][2] = -1.0;\n\n    output->J[0][3] = 0.0;\n    output->J[0][4] = 0.0;\n    output->J[0][5] = 1.0;\n\n    output->J_dot[0][0] = 0;\n    output->J_dot[0][1] = 0;\n    output->J_dot[0][2] = 0;\n\n    output->J_dot[0][3] = 0;\n    output->J_dot[0][4] = 0;\n    output->J_dot[0][5] = 0;\n\n    output->kd[0] = m_kd;\n    output->ks[0] = m_ks;\n\n    output->v_bias[0] = 0;\n\n    constexpr double airDensity =\n        units::AirMolecularMass * units::pressure(1.0, units::atm)\n        / (constants::R * units::celcius(25.0));\n    const double v = m_vehicle->getSpeed();\n    const double v_squared = v * v;\n    const double c_d = m_vehicle->getDragCoefficient();\n    const double A = m_vehicle->getCrossSectionArea();\n    const double rollingResistance = m_vehicle->getRollingResistance();\n\n    output->limits[0][0] =\n        -m_vehicle->linearForceToVirtualTorque(rollingResistance + 0.5 * airDensity * v_squared * c_d * A);\n    output->limits[0][1] = 0;\n}\n"
  },
  {
    "path": "src/vtec_valvetrain.cpp",
    "content": "#include \"../include/vtec_valvetrain.h\"\n\n#include \"../include/engine.h\"\n\nVtecValvetrain::VtecValvetrain() {\n    m_intakeCamshaft = nullptr;\n    m_exhaustCamshaft = nullptr;\n\n    m_vtecIntakeCamshaft = nullptr;\n    m_vtecExhaustCamshaft = nullptr;\n\n    m_engine = nullptr;\n\n    m_minRpm = 0.0;\n    m_minSpeed = 0.0;\n    m_minThrottlePosition = 0.0;\n    m_manifoldVacuum = 0.0;\n}\n\nVtecValvetrain::~VtecValvetrain() {\n    /* void */\n}\n\nvoid VtecValvetrain::initialize(const Parameters &parameters) {\n    m_intakeCamshaft = parameters.intakeCamshaft;\n    m_exhaustCamshaft = parameters.exhaustCamshaft;\n    m_vtecIntakeCamshaft = parameters.vtecIntakeCamshaft;\n    m_vtecExhaustCamshaft = parameters.vtexExhaustCamshaft;\n\n    m_minRpm = parameters.minRpm;\n    m_minSpeed = parameters.minSpeed;\n    m_minThrottlePosition = parameters.minThrottlePosition;\n    m_manifoldVacuum = parameters.manifoldVacuum;\n    m_engine = parameters.engine;\n}\n\ndouble VtecValvetrain::intakeValveLift(int cylinder) {\n    return isVtecEnabled()\n        ? m_vtecIntakeCamshaft->valveLift(cylinder)\n        : m_intakeCamshaft->valveLift(cylinder);\n}\n\ndouble VtecValvetrain::exhaustValveLift(int cylinder) {\n    return isVtecEnabled()\n        ? m_vtecExhaustCamshaft->valveLift(cylinder)\n        : m_exhaustCamshaft->valveLift(cylinder);\n}\n\nCamshaft *VtecValvetrain::getActiveIntakeCamshaft() {\n    return isVtecEnabled()\n        ? m_vtecIntakeCamshaft\n        : m_intakeCamshaft;\n}\n\nCamshaft *VtecValvetrain::getActiveExhaustCamshaft() {\n    return isVtecEnabled()\n        ? m_vtecExhaustCamshaft\n        : m_exhaustCamshaft;\n}\n\nbool VtecValvetrain::isVtecEnabled() const {\n    return\n        m_engine->getManifoldPressure() > m_manifoldVacuum\n        && m_engine->getSpeed() > m_minRpm\n        && (1 - m_engine->getThrottle()) > m_minThrottlePosition;\n}\n"
  },
  {
    "path": "test/function_test.cpp",
    "content": "#include <gtest/gtest.h>\n\n#include \"../include/function.h\"\n\n#include <stdlib.h>\n\nTEST(FunctionTests, FunctionSanityCheck) {\n    Function f;\n    f.initialize(16, 1.0);\n    f.destroy();\n}\n\nTEST(FunctionTests, FunctionTriangleFilterTest) {\n    Function f;\n    f.initialize(0, 1.0);\n    for (int i = 0; i < 10; ++i) {\n        f.addSample((double)i, (double)i * 2);\n    }\n\n    EXPECT_NEAR(f.sampleTriangle(-1.0), 0.0, 1E-6);\n    EXPECT_NEAR(f.sampleTriangle(11.0), 18.0, 1E-6);\n\n    for (int i = 0; i < 10; ++i) {\n        EXPECT_NEAR(f.sampleTriangle((double)i), (double)i * 2, 1E-6);\n    }\n\n    f.destroy();\n}\n\nTEST(FunctionTests, FunctionClosestTest) {\n    Function f;\n    f.initialize(0, 1.0);\n    f.addSample(0.0, 1.0);\n    f.addSample(2.0, 1.0);\n    f.addSample(3.0, 1.1);\n    f.addSample(1.0, 1.0);\n    f.addSample(5.0, 10.0);\n    f.addSample(4.0, 9.0);\n\n    EXPECT_EQ(f.closestSample(2.4), 2);\n    EXPECT_EQ(f.closestSample(6.0), 5);\n\n    f.destroy();\n}\n\nTEST(FunctionTests, FunctionRandomAddTest) {\n    Function f;\n    f.initialize(0, 1.0);\n\n    for (int i = 0; i < 1000; ++i) {\n        f.addSample(rand() % 1000, i);\n    }\n\n    EXPECT_TRUE(f.isOrdered());\n\n    f.destroy();\n}\n\nTEST(FunctionTests, FunctionGaussianTest) {\n    Function f;\n    f.initialize(0, 1.0);\n    f.addSample(0.0, 1.0);\n    f.addSample(2.0, 1.0);\n    f.addSample(3.0, 5.0);\n    f.addSample(1.0, 1.0);\n    f.addSample(5.0, 10.0);\n    f.addSample(4.0, 9.0);\n\n    EXPECT_NEAR(f.sampleGaussian(2.0), 1.0, 0.1);\n    EXPECT_NEAR(f.sampleGaussian(4.0), 9.0, 0.3);\n    EXPECT_NEAR(f.sampleGaussian(100.0), 10.0, 1E-3);\n    EXPECT_NEAR(f.sampleGaussian(-100.0), 1.0, 1E-3);\n\n    for (double s = 2.0; s <= 3.0; s += 0.001) {\n        const double v = f.sampleGaussian(s);\n        std::cerr << v << \"\\n\";\n    }\n\n    f.destroy();\n}\n"
  },
  {
    "path": "test/gas_system_tests.cpp",
    "content": "#include <gtest/gtest.h>\n\n#include \"../include/gas_system.h\"\n#include \"../include/units.h\"\n#include \"../include/csv_io.h\"\n\n#include <sstream>\n\nTEST(GasSystemTests, GasSystemSanity) {\n    GasSystem system;\n    system.initialize(0.0, 0.0, 0.0);\n}\n\nTEST(GasSystemTests, AdiabaticEnergyConservation) {\n    constexpr double pistonArea = units::area(1.0, units::cm2);\n    constexpr double vesselHeight = units::distance(1.0, units::cm);\n    const double compression = vesselHeight * 0.5;\n    const int steps = 10000;\n\n    GasSystem system;\n    system.initialize(\n        units::pressure(1.0, units::atm),\n        units::volume(1.0, units::cc),\n        units::celcius(25.0)\n    );\n\n    const double initialSystemEnergy = system.kineticEnergy();\n    const double initialMolecules = system.n();\n\n    double W = 0.0;\n    double currentPistonHeight = vesselHeight;\n    for (int i = 1; i <= steps; ++i) {\n        const double newPistonHeight = vesselHeight - (compression / steps) * i;\n        const double dH = (currentPistonHeight - newPistonHeight);\n        const double F = system.pressure() * pistonArea;\n        W += F * dH;\n\n        system.changeVolume((newPistonHeight - currentPistonHeight) * pistonArea);\n\n        currentPistonHeight = newPistonHeight;\n    }\n\n    const double finalSystemEnergy = system.kineticEnergy();\n    const double finalMolecules = system.n();\n\n    EXPECT_NEAR(finalMolecules, initialMolecules, 1E-6);\n    EXPECT_NEAR(finalSystemEnergy - initialSystemEnergy, W, 1E-4);\n}\n\nTEST(GasSystemTests, PressureEqualizationEnergyConservation) {\n    GasSystem system1, system2;\n    system1.initialize(\n        units::pressure(1.0, units::atm),\n        units::volume(1000.0, units::cc),\n        units::celcius(25.0)\n    );\n\n    system2.initialize(\n        units::pressure(2.0, units::atm),\n        units::volume(1000.0, units::cc),\n        units::celcius(25.0)\n    );\n\n    const double initialSystemEnergy = system1.totalEnergy() + system2.totalEnergy();\n    const double initialMolecules = system1.n() + system2.n();\n\n    GasSystem::FlowParameters params;\n    params.k_flow = 0.000001;\n    params.crossSectionArea_0 = 1.0;\n    params.crossSectionArea_1 = 1.0;\n    params.direction_x = 1.0;\n    params.direction_y = 0.0;\n    params.dt = 1.0;\n    params.system_0 = &system1;\n    params.system_1 = &system2;\n\n    const double dt = 1 / 100.0;\n    const int steps = 1000;\n    for (int i = 1; i <= steps; ++i) {\n        GasSystem::flow(params);\n    }\n\n    const double finalSystemEnergy = system1.totalEnergy() + system2.totalEnergy();\n    const double finalMolecules = system1.n() + system2.n();\n\n    const double p0 = system1.pressure();\n    const double p1 = system2.pressure();\n\n    EXPECT_NEAR(finalMolecules, initialMolecules, 1E-6);\n    EXPECT_NEAR(finalSystemEnergy, initialSystemEnergy, 1E-4);\n}\n\nTEST(GasSystemTests, PressureEquilibriumMaxFlow) {\n    GasSystem system1, system2;\n    system1.initialize(\n        units::pressure(1.0, units::atm),\n        units::volume(1.0, units::cc),\n        units::celcius(2500.0)\n    );\n\n    system2.initialize(\n        units::pressure(2.0, units::atm),\n        units::volume(1.0, units::cc),\n        units::celcius(25.0)\n    );\n\n    const double maxFlowIn = system1.pressureEquilibriumMaxFlow(&system2);\n\n    system1.loseN(maxFlowIn, system1.kineticEnergyPerMol());\n    system2.gainN(maxFlowIn, system1.kineticEnergyPerMol(), system1.mix());\n\n    EXPECT_NEAR(system1.pressure(), system2.pressure(), 1E-6);\n\n    system1.changePressure(units::pressure(100.0, units::atm));\n\n    const double maxFlowOut = system1.pressureEquilibriumMaxFlow(&system2);\n\n    system1.loseN(maxFlowIn, system1.kineticEnergyPerMol());\n    system2.gainN(maxFlowIn, system1.kineticEnergyPerMol(), system1.mix());\n\n    EXPECT_NEAR(system1.pressure(), system2.pressure(), 1E-6);\n}\n\nTEST(GasSystemTests, PressureEquilibriumMaxFlowInfinite) {\n    GasSystem system1;\n    system1.initialize(\n        units::pressure(1.0, units::atm),\n        units::volume(1.0, units::cc),\n        units::celcius(25.0)\n    );\n\n    constexpr double P_env = units::pressure(2.0, units::atm);\n    constexpr double T_env = units::celcius(25.0);\n\n    const double maxFlow = system1.pressureEquilibriumMaxFlow(P_env, T_env);\n    const double E_k_per_mol = GasSystem::kineticEnergyPerMol(T_env, system1.degreesOfFreedom());\n\n    system1.gainN(maxFlow, E_k_per_mol);\n\n    EXPECT_NEAR(system1.pressure(), P_env, 1E-6);\n}\n\nTEST(GasSystemTests, PressureEquilibriumMaxFlowInfiniteOverpressure) {\n    GasSystem system1;\n    system1.initialize(\n        units::pressure(100.0, units::atm),\n        units::volume(1.0, units::m3),\n        units::celcius(2500.0)\n    );\n\n    constexpr double P_env = units::pressure(2.0, units::atm);\n    constexpr double T_env = units::celcius(25.0);\n\n    const double maxFlow = system1.pressureEquilibriumMaxFlow(P_env, T_env);\n\n    system1.loseN(maxFlow, GasSystem::kineticEnergyPerMol(T_env, 5));\n\n    EXPECT_NEAR(system1.pressure(), P_env, 1E-6);\n}\n\nTEST(GasSystemTests, FlowVariableVolume) {\n    GasSystem system1;\n    system1.initialize(\n        units::pressure(100.0, units::atm),\n        units::volume(1.0, units::m3),\n        units::celcius(25.0)\n    );\n\n    constexpr double P_env = units::pressure(2.0, units::atm);\n    constexpr double T_env = units::celcius(25.0);\n\n    const double maxFlow = system1.pressureEquilibriumMaxFlow(P_env, T_env);\n\n    constexpr double dV = units::volume(1000000.0, units::cc) / 100;\n    for (int i = 0; i < 100; ++i) {\n        const double flowRate0 = system1.flow(0.01, 1/60.0, units::pressure(0.1, units::atm), units::celcius(25.0));\n        const double flowRate1 = system1.flow(0.01, 1 / 60.0, units::pressure(0.2, units::atm), units::celcius(25.0));\n        system1.changeVolume(-dV);\n        system1.changeTemperature(100);\n\n        std::cerr << flowRate0 << \", \" << flowRate1 << \"\\n\";\n    }\n}\n\nTEST(GasSystemTests, PowerStrokeTest) {\n    GasSystem system1;\n    system1.initialize(\n        units::pressure(100.0, units::atm),\n        units::volume(1.0, units::m3),\n        units::celcius(2000.0)\n    );\n\n    constexpr double dV = units::volume(1000000.0, units::cc) / 100;\n    for (int i = 0; i < 100; ++i) {\n        const double flowRate0 = system1.flow(1.0, 1 / 60.0, units::pressure(1.0, units::atm), units::celcius(25.0));\n        std::cerr << i << \", \" << flowRate0 << \", \" << system1.pressure() << \"\\n\";\n    }\n}\n\nTEST(GasSystemTests, IntakeStrokeTest) {\n    atg_csv::CsvData csv;\n    csv.initialize();\n    csv.m_columns = 3;\n\n    GasSystem system1, system2;\n    system1.initialize(\n        units::pressure(1.0, units::atm),\n        units::volume(1000.0, units::m3),\n        units::celcius(25.0)\n    );\n    system2.initialize(\n        units::pressure(1.0, units::atm),\n        units::volume(1.0, units::m3),\n        units::celcius(25.0)\n    );\n\n    csv.write(\"t\");\n    csv.write(\"n\");\n    csv.write(\"theoretical\");\n\n    constexpr double dV = units::volume(4.0, units::m3);\n    constexpr double dt = 0.01;\n    system2.changeVolume(dV * dt * 100);\n    for (int i = 0; i < 100; ++i) {\n        GasSystem::FlowParameters flowParams;\n        flowParams.crossSectionArea_0 = 1.0;\n        flowParams.crossSectionArea_1 = 1.0;\n        flowParams.direction_x = 1.0;\n        flowParams.direction_y = 0.0;\n        flowParams.dt = dt;\n        flowParams.k_flow = GasSystem::k_carb(100000.0);\n        flowParams.system_0 = &system1;\n        flowParams.system_1 = &system2;\n\n        GasSystem::flow(flowParams);\n        //system2.changeVolume(dV * dt);\n        //system2.changeTemperature(units::celcius(25.0) - system2.temperature());\n\n        csv.write(std::to_string(i * dt).c_str());\n        csv.write(std::to_string(system2.n()).c_str());\n        csv.write(std::to_string(\n            units::pressure(1.0, units::atm) * system2.volume()\n            / (constants::R * units::celcius(25.0))).c_str());\n    }\n\n    csv.m_rows = 100 + 1;\n    csv.writeCsv(\"intake_stroke.csv\");\n    csv.destroy();\n}\n\nTEST(GasSystemTests, FlowLimit) {\n    GasSystem system1;\n    system1.initialize(\n        units::pressure(100.0, units::atm),\n        units::volume(1.0, units::m3),\n        units::celcius(2000.0)\n    );\n\n    constexpr double P_env = units::pressure(1.0, units::atm);\n    constexpr double T_env = units::celcius(25.0);\n    const double maxFlow = system1.pressureEquilibriumMaxFlow(P_env, T_env);\n\n    system1.flow(15.0, 10.0, P_env, T_env);\n\n    EXPECT_NEAR(system1.pressure(), P_env, 1E-6);\n}\n\nTEST(GasSystemTests, IdealGasLaw) {\n    GasSystem system1;\n    system1.initialize(\n        units::pressure(100.0, units::atm),\n        units::volume(1.0, units::m3),\n        units::celcius(2000.0)\n    );\n\n    const double PV = system1.pressure() * system1.volume();\n    const double nRT = system1.n() * constants::R * system1.temperature();\n\n    EXPECT_NEAR(PV, nRT, 1E-6);\n}\n\nTEST(GasSystemTests, CompositionSanityCheck) {\n    GasSystem::Mix a, b;\n    a.p_fuel = 1.0;\n    a.p_inert = 1.0;\n    a.p_o2 = 1.0;\n\n    b.p_fuel = 0.0;\n    b.p_inert = 0.0;\n    b.p_o2 = 0.0;\n\n    GasSystem system1;\n    system1.initialize(\n        units::pressure(100.0, units::atm),\n        units::volume(100.0, units::m3),\n        units::celcius(2000.0),\n        a\n    );\n\n    GasSystem system2;\n    system2.initialize(\n        units::pressure(1.0, units::atm),\n        units::volume(1.0, units::m3),\n        units::celcius(2000.0),\n        b\n    );\n\n    const double PV = system1.pressure() * system1.volume();\n    const double nRT = system1.n() * constants::R * system1.temperature();\n\n    GasSystem::FlowParameters params;\n    params.k_flow = 1.0;\n    params.crossSectionArea_0 = 1.0;\n    params.crossSectionArea_1 = 1.0;\n    params.direction_x = 1.0;\n    params.direction_y = 0.0;\n    params.dt = 1 / 60.0;\n    params.system_0 = &system1;\n    params.system_1 = &system2;\n\n    for (int i = 0; i < 200; ++i) {\n        const double flowRate = GasSystem::flow(params);\n        std::cerr << i << \", \" << flowRate << \", \" << system1.pressure() << \"\\n\";\n    }\n\n    EXPECT_NEAR(system2.mix().p_fuel, 1.0, 2E-2);\n    EXPECT_NEAR(system2.mix().p_inert, 1.0, 2E-2);\n    EXPECT_NEAR(system2.mix().p_o2, 1.0, 2E-2);\n}\n\nTEST(GasSystemTests, ChokedFlowTest) {\n    GasSystem system1;\n    system1.initialize(\n        units::pressure(2.5, units::atm),\n        units::volume(1.0, units::m3),\n        units::celcius(2000.0)\n    );\n\n    const double flow_k = GasSystem::flowConstant(\n        units::flow(400, units::scfm),\n        units::pressure(2.5, units::atm),\n        units::pressure(1.5, units::atm),\n        units::celcius(2000.0),\n        GasSystem::heatCapacityRatio(5)\n    );\n\n    const double chokedFlow =\n        system1.flowRate(\n            flow_k,\n            system1.pressure(),\n            units::pressure(1.0, units::atm),\n            system1.temperature(),\n            units::celcius(25),\n            GasSystem::heatCapacityRatio(5),\n            GasSystem::chokedFlowLimit(5),\n            GasSystem::chokedFlowRate(5));\n    const double noncriticalFlow =\n        system1.flowRate(\n            flow_k,\n            system1.pressure(),\n            units::pressure(2.0, units::atm),\n            system1.temperature(),\n            units::celcius(25),\n            GasSystem::heatCapacityRatio(5),\n            GasSystem::chokedFlowLimit(5),\n            GasSystem::chokedFlowRate(5));\n\n    const double chokedFlowScfm = units::convert(chokedFlow, units::scfm);\n    const double noncriticalFlowScfm = units::convert(noncriticalFlow, units::scfm);\n}\n\nTEST(GasSystemTests, CfmConversions) {\n    constexpr double standardPressure = units::pressure(1.0, units::atm);\n    constexpr double standardTemp = units::celcius(25.0);\n    constexpr double airDensity =\n        units::AirMolecularMass * (standardPressure * units::volume(1.0, units::m3))\n        / (constants::R * standardTemp);\n\n    const double flow_28 = GasSystem::k_28inH2O(300);\n    \n    const double flowRate = GasSystem::flowRate(\n        flow_28,\n        units::pressure(1.0, units::atm),\n        units::pressure(1.0, units::atm) - units::pressure(41.0, units::inH2O),\n        units::celcius(25.0),\n        units::celcius(25.0),\n        GasSystem::heatCapacityRatio(5),\n        GasSystem::chokedFlowLimit(5),\n        GasSystem::chokedFlowRate(5));\n\n    const double flowRateCfm = units::convert(flowRate, units::scfm);\n}\n\nTEST(GasSystemTests, FlowRateConstant) {\n    const double flow_k = GasSystem::flowConstant(\n        units::flow(400, units::scfm),\n        units::pressure(2.5, units::atm),\n        units::pressure(0.5, units::atm),\n        units::celcius(2000.0),\n        GasSystem::heatCapacityRatio(5)\n    );\n\n    const double flowRate =\n        GasSystem::flowRate(\n            flow_k,\n            units::pressure(2.5, units::atm),\n            units::pressure(2.5 - 0.5, units::atm),\n            units::celcius(2000.0),\n            units::celcius(25),\n            GasSystem::heatCapacityRatio(5),\n            GasSystem::chokedFlowLimit(5),\n            GasSystem::chokedFlowRate(5));\n\n    EXPECT_NEAR(flowRate, units::flow(400, units::scfm), 1E-6);\n}\n\nTEST(GasSystemTests, GasVelocityReducesStaticPressure) {\n    atg_csv::CsvData csv;\n    csv.initialize();\n    csv.m_columns = 6;\n\n    GasSystem system1, system2;\n    system1.initialize(\n        units::pressure(15, units::psi),\n        units::volume(300, units::cc),\n        units::celcius(25.0)\n    );\n    system1.setGeometry(units::distance(10, units::cm), units::distance(10, units::cm), 1.0, 0.0);\n\n    system2.initialize(\n        units::pressure(2, units::psi),\n        units::volume(1.0, units::L),\n        units::celcius(25.0)\n    );\n    system2.setGeometry(units::distance(10, units::cm), units::distance(2, units::cm), 1.0, 0.0);\n\n    const double initialSystemEnergy = system1.totalEnergy() + system2.totalEnergy();\n    const double initialMolecules = system1.n() + system2.n();\n\n    GasSystem::FlowParameters params;\n    params.k_flow = GasSystem::k_28inH2O(500.0) * 1.0;\n    params.crossSectionArea_0 = 50.0 * units::cm * units::cm;\n    params.crossSectionArea_1 = 4.0 * units::cm * units::cm;\n    params.direction_x = 1.0;\n    params.direction_y = 0.0;\n    params.dt = 1 / 10000.0;\n    params.system_0 = &system1;\n    params.system_1 = &system2;\n\n    csv.write(\"time\");\n    csv.write(\"P_0\");\n    csv.write(\"P_1\");\n    csv.write(\"v_0\");\n    csv.write(\"v_1\");\n    csv.write(\"total_energy\");\n\n    const int steps = 1000;\n    for (int i = 1; i <= steps; ++i) {\n        const double staticPressure =\n            system2.pressure() + system2.dynamicPressure(0.0, 1.0);\n        const double totalPressure =\n            system2.pressure() + system2.dynamicPressure(1.0, 0.0);\n\n        const double systemEnergy = system1.totalEnergy() + system2.totalEnergy();\n        const double velocity_x_0 = system1.velocity_x();\n        const double velocity_x_1 = system2.velocity_x();\n\n        const double P_0 = system1.pressure();\n        const double P_1 = system2.pressure() + system2.dynamicPressure(-1.0, 0.0);\n\n        GasSystem::flow(params);\n        system1.updateVelocity(params.dt);\n        system2.updateVelocity(params.dt);\n\n        //system1.dissipateVelocity(params.dt, 0.01);\n        //system2.dissipateVelocity(params.dt, 0.01);\n\n        ++csv.m_rows;\n        csv.write(std::to_string(i * params.dt).c_str());\n        csv.write(std::to_string(P_0).c_str());\n        csv.write(std::to_string(P_1).c_str());\n        csv.write(std::to_string(velocity_x_0).c_str());\n        csv.write(std::to_string(velocity_x_1).c_str());\n        csv.write(std::to_string(systemEnergy).c_str());\n    }\n\n    const double finalSystemEnergy = system1.totalEnergy() + system2.totalEnergy();\n    const double finalMolecules = system1.n() + system2.n();\n\n    const double p0 = system1.pressure();\n    const double p1 = system2.pressure();\n\n    EXPECT_NEAR(finalMolecules, initialMolecules, 1E-6);\n    EXPECT_NEAR(finalSystemEnergy, initialSystemEnergy, 1E-4);\n\n    csv.writeCsv(\"gas_system_test_output.csv\", nullptr, '\\t');\n    csv.destroy();\n}\n\nTEST(GasSystemTests, GasVelocityProducesScavengingEffect) {\n    atg_csv::CsvData csv;\n    csv.initialize();\n    csv.m_columns = 7;\n\n    constexpr double cylinderArea =\n        constants::pi * units::distance(2.0, units::inch) * units::distance(2.0, units::inch);\n    constexpr double tubeArea =\n        constants::pi * units::distance(1.75 / 2, units::inch) * units::distance(1.75 / 2, units::inch);\n\n    GasSystem system1, system2, atmosphere;\n    system1.initialize(\n        units::pressure(1000, units::psi),\n        units::volume(1000, units::cc),\n        units::celcius(1000.0)\n    );\n    system1.setGeometry(\n        units::distance(10.0, units::cm),\n        units::distance(1.0, units::cm),\n        1.0,\n        0.0);\n\n    system2.initialize(\n        units::pressure(15, units::psi),\n        tubeArea * units::distance(50.0, units::inch),\n        units::celcius(25.0)\n    );\n    system2.setGeometry(\n        units::distance(50.0, units::inch),\n        std::sqrt(tubeArea),\n        1.0,\n        0.0);\n\n    atmosphere.initialize(\n        units::pressure(15, units::psi),\n        units::volume(10000, units::m3),\n        units::celcius(25.0)\n    );\n\n    const double initialSystemEnergy =\n        system1.totalEnergy()\n        + system2.totalEnergy()\n        + atmosphere.totalEnergy();\n    const double initialMolecules = system1.n() + system2.n();\n\n    const double atmosphereArea =\n        1000.0;\n\n    GasSystem::FlowParameters params;\n    params.k_flow = GasSystem::k_28inH2O(230.0) * 1.0;        \n    params.direction_x = 1.0;\n    params.direction_y = 0.0;\n    params.dt = 1 / (16 * 4000.0);\n    params.system_0 = &system1;\n    params.system_1 = &system2;\n\n    csv.write(\"iteration\");\n    csv.write(\"time\");\n    csv.write(\"static_cylinder_pressure\");\n    csv.write(\"exhaust_pressure\");\n    csv.write(\"v_0\");\n    csv.write(\"v_1\");\n    csv.write(\"exhaust_static_pressure\");\n\n    const int steps = 10000;\n    for (int i = 1; i <= steps; ++i) {\n        const double staticPressure =\n            system2.pressure() + system2.dynamicPressure(0.0, 1.0);\n        const double totalPressure =\n            system2.pressure() + system2.dynamicPressure(1.0, 0.0);\n\n        const double systemEnergy0 =\n            system1.totalEnergy()\n            + system2.totalEnergy()\n            + atmosphere.totalEnergy();\n        const double velocity_x_0 = system1.velocity_x();\n        const double velocity_x_1 = system2.velocity_x();\n\n        const double P_0 = system1.pressure();\n        const double P_1 = system2.pressure() + system2.dynamicPressure(1.0, 0.0);\n        const double exhaustStaticPressure = system2.pressure();\n\n        if (system1.volume() > units::volume(118.0, units::cc)) {\n            system1.changeVolume(-units::volume(200000.0, units::cc) * params.dt);\n        }\n        else {\n            system1.setVolume(units::volume(118.0, units::cc));\n        }\n\n        params.system_0 = &system1;\n        params.system_1 = &system2;\n        params.crossSectionArea_0 = cylinderArea;\n        params.crossSectionArea_1 = tubeArea;\n        params.k_flow = GasSystem::k_28inH2O(230.0);\n        GasSystem::flow(params);\n\n        const double systemEnergy1 =\n            system1.totalEnergy()\n            + system2.totalEnergy()\n            + atmosphere.totalEnergy();\n\n        params.system_0 = &system2;\n        params.system_1 = &atmosphere;\n        params.crossSectionArea_0 = tubeArea;\n        params.crossSectionArea_1 = atmosphereArea;\n        params.k_flow = GasSystem::k_carb(1000);\n        GasSystem::flow(params);\n\n        const double systemEnergy2 =\n            system1.totalEnergy()\n            + system2.totalEnergy()\n            + atmosphere.totalEnergy();\n\n        system1.updateVelocity(params.dt);\n        system2.updateVelocity(params.dt);\n        system1.dissipateExcessVelocity();\n        system2.dissipateExcessVelocity();\n\n        ++csv.m_rows;\n        csv.write(std::to_string(i).c_str());\n        csv.write(std::to_string(i * params.dt).c_str());\n        csv.write(std::to_string(P_0).c_str());\n        csv.write(std::to_string(P_1).c_str());\n        csv.write(std::to_string(velocity_x_0).c_str());\n        csv.write(std::to_string(velocity_x_1).c_str());\n        csv.write(std::to_string(exhaustStaticPressure).c_str());\n    }\n\n    csv.writeCsv(\"gas_system_test_output.csv\", nullptr, '\\t');\n    csv.destroy();\n}\n\nTEST(GasSystemTests, GasVelocityProducesRamEffect) {\n    atg_csv::CsvData csv;\n    csv.initialize();\n    csv.m_columns = 9;\n\n    constexpr double cylinderArea =\n        constants::pi * units::distance(2.0, units::inch) * units::distance(2.0, units::inch);\n    constexpr double runnerArea =\n        constants::pi * units::distance(0.5, units::inch) * units::distance(0.5, units::inch);\n\n    GasSystem cylinder, runner, atmosphere;\n    cylinder.initialize(\n        units::pressure(1.0, units::atm),\n        units::volume(118, units::cc),\n        units::celcius(25.0)\n    );\n    cylinder.setGeometry(\n        units::distance(10.0, units::cm),\n        units::distance(1.0, units::cm),\n        1.0,\n        0.0);\n\n    runner.initialize(\n        units::pressure(1.0, units::atm),\n        units::volume(320, units::cc),\n        units::celcius(25.0)\n    );\n    runner.setGeometry(\n        units::distance(5.0, units::inch),\n        std::sqrt(runnerArea),\n        1.0,\n        0.0);\n\n    atmosphere.initialize(\n        units::pressure(1.0, units::atm),\n        units::volume(10000, units::m3),\n        units::celcius(25.0)\n    );\n\n    const double initialSystemEnergy =\n        cylinder.totalEnergy()\n        + runner.totalEnergy()\n        + atmosphere.totalEnergy();\n    const double initialMolecules = cylinder.n() + runner.n();\n\n    const double atmosphereArea =\n        1000.0;\n\n    GasSystem::FlowParameters params;\n    params.dt = 1 / (16 * 4000.0);\n    params.direction_x = 1.0;\n    params.direction_y = 0.0;\n\n    csv.write(\"iteration\");\n    csv.write(\"time\");\n    csv.write(\"crank_angle\");\n    csv.write(\"cylinder_volume\");\n    csv.write(\"n_mol\");\n    csv.write(\"cylinder_air_velocity\");\n    csv.write(\"runner_air_velocity\");\n    csv.write(\"cylinder_pressure\");\n    csv.write(\"runner_pressure\");\n\n    constexpr double speed = 3000; // rpm\n    constexpr double stroke = units::distance(4.0, units::inch);\n\n    double max_n = 0.0;\n    double max_n_angle = 0.0;\n\n    double max_v = 0.0;\n    double max_v_angle = 0.0;\n\n    double flow = 1.0;\n\n    const int steps = 10000;\n    for (int i = 1; i <= steps; ++i) {\n        const double velocity_x_0 = cylinder.velocity_x();\n        const double velocity_x_1 = runner.velocity_x();\n\n        const double P_0 = cylinder.pressure();\n        const double P_1 = runner.pressure() + runner.dynamicPressure(1.0, 0.0);\n        const double exhaustStaticPressure = runner.pressure();\n\n        const double t = i * params.dt;\n        const double pistonHeight =\n            units::distance(0.25, units::inch)\n            + stroke / 2\n            + (stroke / 2) * -std::cos(2 * constants::pi * (t * (speed / 60)));\n\n        cylinder.setVolume(pistonHeight * cylinderArea);\n        if (i == 1) {\n            cylinder.changePressure(units::pressure(1.0, units::atm) - cylinder.pressure());\n        }\n\n        params.system_0 = &runner;\n        params.system_1 = &cylinder;\n        params.crossSectionArea_0 = runnerArea;\n        params.crossSectionArea_1 = cylinderArea;\n        params.k_flow = GasSystem::k_28inH2O(230.0) * flow;\n        GasSystem::flow(params);\n\n        params.system_0 = &atmosphere;\n        params.system_1 = &runner;\n        params.crossSectionArea_0 = atmosphereArea;\n        params.crossSectionArea_1 = cylinderArea;\n        params.k_flow = GasSystem::k_carb(500);\n        GasSystem::flow(params);\n\n        cylinder.updateVelocity(params.dt, 1.0);\n        runner.updateVelocity(params.dt, 0.1);\n        cylinder.dissipateExcessVelocity();\n        runner.dissipateExcessVelocity();\n\n        const double angle = 360 * (t * (speed / 60));\n\n        if (cylinder.n() > max_n) {\n            max_n = cylinder.n();\n            max_n_angle = angle;\n        }\n\n        if (cylinder.volume() > max_v) {\n            max_v = cylinder.volume();\n            max_v_angle = angle;\n        }\n\n        ++csv.m_rows;\n        csv.write(std::to_string(i).c_str());\n        csv.write(std::to_string(i * params.dt).c_str());\n        csv.write(std::to_string(angle).c_str());\n        csv.write(std::to_string(cylinder.volume()).c_str());\n        csv.write(std::to_string(cylinder.n()).c_str());\n        csv.write(std::to_string(velocity_x_0).c_str());\n        csv.write(std::to_string(velocity_x_1).c_str());\n        csv.write(std::to_string(P_0).c_str());\n        csv.write(std::to_string(P_1).c_str());\n    }\n\n    csv.writeCsv(\"gas_system_test_output.csv\", nullptr, '\\t');\n    csv.destroy();\n\n    const double delay = max_n_angle - max_v_angle;\n    const double m_n = max_n;\n\n    int a = 0;\n}\n\nTEST(GasSystemTests, GasVelocityStabilizesInClosedSystem) {\n    atg_csv::CsvData csv;\n    csv.initialize();\n    csv.m_columns = 2;\n\n    GasSystem system1;\n    system1.initialize(\n        units::pressure(100, units::psi),\n        units::volume(1000, units::cc),\n        units::celcius(1000.0)\n    );\n    system1.setGeometry(\n        units::distance(10.0, units::cm),\n        units::distance(10.0, units::cm),\n        1.0,\n        0.0);\n\n    const double initialSystemEnergy = system1.totalEnergy();\n    const double initialMolecules = system1.n();\n\n    csv.write(\"time\");\n    csv.write(\"velocity\");\n\n    const int steps = 10000;\n    const double dt = 1 / 10000.0;\n    for (int i = 1; i <= steps; ++i) {\n        system1.updateVelocity(dt);\n\n        ++csv.m_rows;\n        csv.write(std::to_string(i * dt).c_str());\n        csv.write(std::to_string(system1.velocity_x()).c_str());\n    }\n\n    csv.writeCsv(\"gas_system_test_output.csv\", nullptr, '\\t');\n    csv.destroy();\n}\n"
  },
  {
    "path": "test/synthesizer_tests.cpp",
    "content": "#include <gtest/gtest.h>\n\n#include \"../include/synthesizer.h\"\n\n#include <chrono>\n\nusing namespace std::chrono_literals;\n\nvoid setupStandardSynthesizer(Synthesizer &synth) {\n    Synthesizer::Parameters params;\n    params.audioBufferSize = 512 * 16;\n    params.audioSampleRate = 16;\n    params.inputBufferSize = 256;\n    params.inputChannelCount = 8;\n    params.inputSampleRate = 32;\n\n    Synthesizer::AudioParameters audioParams;\n    audioParams.airNoise = 0.0;\n    audioParams.inputSampleNoise = 0.0;\n    audioParams.levelerMaxGain = 1.0;\n    audioParams.levelerMinGain = 1.0;\n    audioParams.dF_F_mix = 0.0;\n    params.initialAudioParameters = audioParams;\n\n    synth.initialize(params);\n}\n\nvoid setupSynchronizedSynthesizer(Synthesizer &synth) {\n    Synthesizer::Parameters params;\n    params.audioBufferSize = 512 * 16;\n    params.audioSampleRate = 32;\n    params.inputBufferSize = 1024;\n    params.inputChannelCount = 8;\n    params.inputSampleRate = 32;\n\n    Synthesizer::AudioParameters audioParams;\n    audioParams.airNoise = 0.0;\n    audioParams.inputSampleNoise = 0.0;\n    audioParams.levelerMaxGain = 1.0;\n    audioParams.levelerMinGain = 1.0;\n    audioParams.dF_F_mix = 0.0;\n    params.initialAudioParameters = audioParams;\n\n    synth.initialize(params);\n}\n\nTEST(SynthesizerTests, SynthesizerSanityCheck) {\n    Synthesizer synth;\n    setupStandardSynthesizer(synth);\n    synth.destroy();\n}\n/*\nTEST(SynthesizerTests, SynthesizerConversionTest) {\n    Synthesizer synth;\n    setupStandardSynthesizer(synth);\n\n    EXPECT_NEAR(synth.inputSampleToTimeOffset(0.0), 0.0, 1E-6);\n    EXPECT_NEAR(synth.inputSampleToTimeOffset(1.0), 1 / 32.0, 1E-6);\n\n    EXPECT_NEAR(synth.audioSampleToTimeOffset(0), -0.5, 1E-6);\n\n    synth.destroy();\n}\n\nTEST(SynthesizerTests, SynthesizerTrimTest) {\n    Synthesizer synth;\n    setupStandardSynthesizer(synth);\n\n    const double timeOffset0 = synth.audioSampleToTimeOffset(0);\n\n    synth.trimInput(0.5, false);\n\n    const double timeOffset1 = synth.audioSampleToTimeOffset(8);\n\n    EXPECT_NEAR(timeOffset1, timeOffset0, 1E-6);\n\n    synth.destroy();\n}\n\nTEST(SynthesizerTests, SynthesizerSampleTest) {\n    Synthesizer synth;\n    setupStandardSynthesizer(synth);\n\n    for (int i = 0; i < 1024; ++i) {\n        const double v = (double)i;\n        const double data[] = { v, v, v, v, v, v, v, v };\n        synth.writeInput(data);\n    }\n\n    const double end_t = 1023 / 32.0;\n\n    const double v0 = synth.sampleInput(end_t, 0);\n    const double v1 = synth.sampleInput(end_t - 1 / 64.0, 0);\n\n    EXPECT_NEAR(v0, 1023.0, 1E-6);\n    EXPECT_NEAR(v1, 1022.5, 1E-6);\n\n    synth.trimInput(0.5);\n\n    const double v0_trim = synth.sampleInput(end_t, 0);\n    const double v1_trim = synth.sampleInput(end_t - 1 / 64.0, 0);\n\n    EXPECT_NEAR(v0, v0_trim, 1E-6);\n    EXPECT_NEAR(v1, v1_trim, 1E-6);\n\n    synth.destroy();\n}\n*/\n\nTEST(SynthesizerTests, SynthesizerSystemTestSingleThread) {\n    constexpr int inputSamples = 64;\n    constexpr int outputSamples = 63;\n\n    Synthesizer synth;\n    setupSynchronizedSynthesizer(synth);\n\n    int16_t *output = new int16_t[outputSamples];\n    int totalSamples = 0;\n\n    for (int i = 0; i < inputSamples;) {\n        for (int j = 0; j < 16; ++j, ++i) {\n            const double v = (double)i;\n            const double data[] = { v, v, v, v, v, v, v, v };\n            synth.writeInput(data);\n        }\n\n        synth.endInputBlock();\n        synth.renderAudio();\n\n        totalSamples += synth.readAudioOutput(16, output + totalSamples);\n        int a = 0;\n    }\n\n    const int rem = synth.readAudioOutput(outputSamples - totalSamples, output + totalSamples);\n\n    EXPECT_EQ(rem, outputSamples - totalSamples);\n\n    for (int i = 0; i < 16; ++i) {\n        EXPECT_EQ(output[i], 0);\n    }\n\n    for (int i = 16; i < outputSamples; ++i) {\n        EXPECT_EQ(output[i], (i - 16) * 10 * 8);\n    }\n\n    synth.destroy();\n    delete[] output;\n}\n\nTEST(SynthesizerTests, SynthesizerSystemTest) {\n    constexpr int inputSamples = 1024;\n    constexpr int outputSamples = 1023;\n\n    Synthesizer synth;\n    setupSynchronizedSynthesizer(synth);\n    synth.startAudioRenderingThread();\n\n    int16_t *output = new int16_t[outputSamples];\n    int totalSamples = 0;\n\n    for (int i = 0; i < inputSamples;) {\n        for (int j = 0; j < 16; ++j, ++i) {\n            const double v = (double)i;\n            const double data[] = { v, v, v, v, v, v, v, v };\n            synth.writeInput(data);\n        }\n\n        const int samplesReturned = synth.readAudioOutput(8, output + totalSamples);\n        totalSamples += samplesReturned;\n    }\n\n    synth.endInputBlock();\n    synth.waitProcessed();\n\n    const int rem = synth.readAudioOutput(outputSamples - totalSamples, output + totalSamples);\n    EXPECT_EQ(rem, outputSamples - totalSamples);\n\n    for (int i = 0; i < 16; ++i) {\n        EXPECT_EQ(output[i], 0);\n    }\n\n    for (int i = 16; i < outputSamples; ++i) {\n        EXPECT_EQ(output[i], std::min(32767, (i - 16) * 10 * 8));\n    }\n\n    synth.endAudioRenderingThread();\n    synth.destroy();\n\n    delete[] output;\n}\n"
  }
]