# How to Debug a Single Nallely VirtualDevice Instance with pdb Without Affecting Other Devices

> Debug a single Nallely VirtualDevice instance with pdb by adding a breakpoint to its thread. Learn how to isolate debugging without impacting other devices in dr schlange nallely midi.

- Repository: [dr-schlange/nallely-midi](https://github.com/dr-schlange/nallely-midi)
- Tags: how-to-guide
- Published: 2026-02-28

---

**You can debug a single Nallely VirtualDevice instance using pdb by inserting a breakpoint inside that device's thread-specific code, since each VirtualDevice runs in its own Python threading.Thread.**

The nallely-midi framework isolates every virtual device in its own execution thread, making it possible to pause and inspect one device while the rest of your MIDI patch continues running. This architecture allows you to use standard Python debugging tools like `pdb.set_trace()` or `breakpoint()` to troubleshoot individual device behavior without freezing your entire application.

## Understanding Thread Isolation in Nallely

Each `VirtualDevice` (and subclasses like `Sequencer` or `Clock`) executes inside an independent `threading.Thread` managed by the framework. In [`nallely/core/virtual_device.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/virtual_device.py), the `VirtualDevice.run()` method at line 357 contains the core processing loop that invokes your device's `main()` method. Because Python's `pdb` operates at the thread level, inserting a breakpoint anywhere inside this loop suspends only that specific thread, leaving all other device threads unaffected.

The framework provides built-in mechanisms to control this isolation:

- **`VirtualDevice.pause()`** (line 615): Clears the device's internal `pause_event`, empties input queues, and stops the run-loop until `resume()` is called
- **`self.debug`** (line 205): A boolean flag initialized to `False` that enables conditional breakpoints for specific instances
- **`set_pause` property**: Allows external threads to pause and resume a device programmatically without modifying the device's source code

## Using the Debug Flag for Conditional Breakpoints

The `VirtualDevice` base class includes a `debug` attribute that you can toggle on a per-instance basis. When `device.debug` is `True`, you can guard your breakpoints to ensure they only trigger for the device you are actively investigating.

Place your breakpoint at the start of your device's `main()` method or within specific event handlers:

```python
from nallely import VirtualDevice, on
from nallely.core.world import ThreadContext

class MySynth(VirtualDevice):
    def main(self, ctx: ThreadContext):
        if self.debug:               # Only breaks when enabled for this instance

            import pdb; pdb.set_trace()
        # Continue with regular processing...

        pass

```

This pattern ensures that even if you create multiple instances of `MySynth`, only the one with `debug=True` will enter the debugger.

## Step-by-Step Debugging Strategy

Follow this workflow to isolate and debug a single device in your Nallely patch:

1. **Create the device instance** and enable debugging: `my_device = MySynth(debug=True)`

2. **Insert conditional breakpoints** in your device's `main()` method or event handlers using `if self.debug: pdb.set_trace()`

3. **Start the device** using `my_device.start()` or let the framework auto-start it

4. **Interact with pdb** when the breakpoint triggers—use `n` (next) to step through code or `c` (continue) to resume execution

5. **Resume normal operation** by continuing in pdb; only this device's thread pauses while others process normally

If you need to pause the device from a test script or another thread without modifying the device code, use the `set_pause` property:

```python
my_device.set_pause = 1   # Pauses only this device

# ... inspect state or modify inputs ...

my_device.set_pause = 0   # Resumes execution

```

## Complete Code Examples

### Minimal Example with Conditional Breakpoint

This example demonstrates debugging a simple counter device without affecting the main execution flow:

```python

# examples/debug_one_device.py

from nallely import VirtualDevice
from nallely.core.world import ThreadContext
import time

class Counter(VirtualDevice):
    output_cv = VirtualDevice.output_cv

    def main(self, ctx: ThreadContext):
        if self.debug:
            import pdb; pdb.set_trace()   # Stops only this thread

        self.output = (self.output or 0) + 1
        return self.output

if __name__ == "__main__":
    counter = Counter(debug=True)   # Enable debug for this instance only

    counter.start()
    
    time.sleep(2)   # Let it run (will hit breakpoint immediately)

    counter.stop()

```

Running this script drops you into pdb after the first cycle, allowing you to inspect `self.output` while the Python interpreter continues running other threads normally.

### Debugging a Device in a Larger Patch

When working with linked devices, you can debug one component while the clock and other devices keep running:

```python
from nallely import Clock, VirtualDevice, on
from nallely.core.world import ThreadContext
import time

class SimpleSynth(VirtualDevice):
    note_cv = VirtualDevice.output_cv

    @on(Clock.output_cv, edge="rising")
    def on_clock(self, value, ctx):
        if self.debug:
            import pdb; pdb.set_trace()
        self.note_cv = int(value * 12) % 128
        return self.note_cv

# Create patch

clock = Clock(speed=1.0, autoconnect=True)
synth = SimpleSynth(debug=True)   # Debug only the synth

synth.bind(clock)

clock.start()
synth.start()

time.sleep(5)   # Clock runs continuously; synth pauses at breakpoint

clock.stop()
synth.stop()

```

Here the `Clock` continues ticking while you step through the `SimpleSynth` logic in pdb.

### Pausing from a Test Script

You can pause devices programmatically without adding breakpoint code:

```python
import time
from nallely import Clock, VirtualDevice

class Dummy(VirtualDevice):
    def main(self, ctx: ThreadContext):
        return None

clock = Clock(speed=2.0, autoconnect=True)
dummy = Dummy()

clock.bind(dummy)
clock.start()
dummy.start()

# Pause only the dummy device externally

dummy.set_pause = 1
time.sleep(1)      # Clock runs, dummy is frozen

dummy.set_pause = 0  # Resume dummy

time.sleep(2)
clock.stop()
dummy.stop()

```

The `set_pause` property calls `pause()` and `resume()` internally, manipulating only that instance's `pause_event` without touching global interpreter state.

## Summary

- **Thread isolation** in `nallely-midi` places each `VirtualDevice` in its own `threading.Thread`, making single-device debugging possible
- **Conditional breakpoints** using `if self.debug:` allow you to target specific instances without affecting others
- **The `debug` flag** (defined at line 205 in [`virtual_device.py`](https://github.com/dr-schlange/nallely-midi/blob/main/virtual_device.py)) controls which instances trigger pdb breakpoints
- **Programmatic pausing** via `set_pause` or `pause()` (line 615) stops individual devices from external threads
- **The `run()` method** (line 357) serves as the primary insertion point for breakpoints within the device's execution loop

## Frequently Asked Questions

### Will pdb stop all devices in a Nallely patch?

No. Because each `VirtualDevice` executes in its own Python thread, `pdb.set_trace()` suspends only the thread where it is called. Other devices in your patch continue processing MIDI events and updating their outputs normally while you debug the paused instance.

### Where should I place breakpoints in a VirtualDevice subclass?

Insert breakpoints at the beginning of your `main()` method or inside event handlers decorated with `@on()`. These locations in [`nallely/core/virtual_device.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/virtual_device.py) run within the device's dedicated thread. Avoid placing breakpoints in `__init__` or module-level code, as those run in the main thread and would pause your entire application.

### Can I pause a device without modifying its source code?

Yes. Use the `set_pause` property available on all `VirtualDevice` instances. Setting `device.set_pause = 1` calls the `pause()` method defined at line 615 of [`virtual_device.py`](https://github.com/dr-schlange/nallely-midi/blob/main/virtual_device.py), which clears the device's internal event loop and stops processing without requiring any changes to the device's implementation.

### Does the debug flag affect performance when not in use?

No. The `debug` attribute defaults to `False` and checking `if self.debug:` adds negligible overhead. Since the breakpoint code never executes when the flag is disabled, you can safely leave conditional checks in production code without impacting the real-time performance of your MIDI processing loop.