========
Tutorial
========

This tutorial will guide you through creating MaxMSP patches with MaxPyLang.

Getting Started
===============

Installation
------------

First, install MaxPyLang:

.. code-block:: bash

    git clone https://github.com/Barnard-PL-Labs/MaxPyLang.git
    cd MaxPyLang
    pip3 install .

Hello World
-----------

Let's create a simple oscillator patch that generates a 440 Hz tone:

.. code-block:: python

    import maxpylang as mp

    # Create a new patch
    patch = mp.MaxPatch()

    # Place objects
    osc = patch.place("cycle~ 440")[0]
    dac = patch.place("ezdac~")[0]

    # Connect them
    patch.connect([osc.outs[0], dac.ins[0]])

    # Save the patch
    patch.save("hello_world.maxpat")

Run this script, then open ``hello_world.maxpat`` in MaxMSP. Click the DAC (ezdac~) to hear the tone!

Basic Concepts
==============

Creating a Patch
----------------

Every MaxPyLang script starts by creating a MaxPatch object:

.. code-block:: python

    import maxpylang as mp
    patch = mp.MaxPatch()

Placing Objects
---------------

Use ``patch.place()`` to add objects to your patch. The method returns a list of created objects:

.. code-block:: python

    # Place a single object
    metro = patch.place("metro 500")[0]

    # Place multiple objects
    oscs = patch.place("cycle~ 220", "cycle~ 330", "cycle~ 440")

You can also specify placement options:

.. code-block:: python

    # Grid placement with custom spacing
    objects = patch.place(
        "cycle~ 100", "cycle~ 200", "cycle~ 300",
        spacing_type="grid",
        spacing=[100, 80]
    )

    # Vertical placement
    objects = patch.place(
        "random 100", "random 200", "random 300",
        spacing_type="vertical",
        spacing=50
    )

Connecting Objects
------------------

Connect objects using their inlets and outlets:

.. code-block:: python

    # Connect outlet 0 of obj1 to inlet 0 of obj2
    patch.connect([obj1.outs[0], obj2.ins[0]])

    # Connect multiple patchcords at once
    patch.connect(
        [obj1.outs[0], obj2.ins[0]],
        [obj1.outs[1], obj3.ins[0]],
        [obj2.outs[0], obj4.ins[0]]
    )

Saving Patches
--------------

Save your patch to a .maxpat file:

.. code-block:: python

    patch.save("my_patch.maxpat")

Working with Inlets and Outlets
================================

Every MaxObject has ``ins`` (inlets) and ``outs`` (outlets) that you can access by index:

.. code-block:: python

    osc = patch.place("cycle~ 440")[0]
    dac = patch.place("ezdac~")[0]

    # Access outlets and inlets by index
    outlet = osc.outs[0]     # First outlet of osc
    inlet = dac.ins[0]       # First inlet of dac

    # Connect them
    patch.connect([outlet, inlet])

Example: Building a Simple Synth
=================================

Let's create a more complex patch with multiple oscillators:

.. code-block:: python

    import maxpylang as mp

    patch = mp.MaxPatch()

    # Create oscillators at different frequencies
    frequencies = [220, 330, 440, 550]
    oscillators = []

    for freq in frequencies:
        osc = patch.place(f"cycle~ {freq}")[0]
        oscillators.append(osc)

    # Create a mixer
    mixer = patch.place("*~ 0.25")[0]  # Scale volume to avoid clipping
    dac = patch.place("ezdac~")[0]

    # Connect all oscillators to mixer
    for osc in oscillators:
        patch.connect([osc.outs[0], mixer.ins[0]])

    # Connect mixer to speakers
    patch.connect([mixer.outs[0], dac.ins[0]])

    patch.save("multi_oscillator.maxpat")

Example: Random Patch Generation
=================================

MaxPyLang makes it easy to generate patches programmatically:

.. code-block:: python

    import maxpylang as mp
    import random

    patch = mp.MaxPatch()

    # Generate random number generators
    num_generators = 5
    generators = []

    for i in range(num_generators):
        max_val = random.randint(50, 500)
        gen = patch.place(f"random {max_val}")[0]
        generators.append(gen)

    # Create outputs
    outputs = []
    for i in range(num_generators):
        out = patch.place("number")[0]
        outputs.append(out)

    # Connect generators to outputs
    for gen, out in zip(generators, outputs):
        patch.connect([gen.outs[0], out.ins[0]])

    # Add a metro to trigger them all
    metro = patch.place("metro 1000")[0]
    for gen in generators:
        patch.connect([metro.outs[0], gen.ins[0]])

    patch.save("random_generators.maxpat")

Loading Existing Patches
=========================

You can load and modify existing MaxMSP patches:

.. code-block:: python

    import maxpylang as mp

    # Load an existing patch
    patch = mp.MaxPatch("existing_patch.maxpat")

    # Add new objects
    new_obj = patch.place("print")[0]

    # Save modified patch
    patch.save("modified_patch.maxpat")

Next Steps
==========

- Explore the :doc:`API/API` for complete reference documentation
- Check out :doc:`topics/topics` for advanced features
- See the ``examples/`` directory for more complex patches
- Learn about :doc:`topics/unknown_objs` and :doc:`topics/linked_files`
