How interpret DJ turntable encoder sensor for Python vinyl timecode synthesis

Hi,

I have a sensor measuring the rotation of a DJ turntable. A magnet is attached to the center top of a vinyl record and a magnetometer encoder sensor detects its rotation.

I have the sensor calibrated. See this video of serial plot.

However I'm having trouble interpreting the readings when using a Python script that will control the playback of DJ software via synthesis of vinyl time code. My initial Python is only interpreting the reading as forward then back as is seen by watching the audio file in my DJ software playing forwards a little way then back again, repeat. See this video next video. You can't see the turntable but when the play head is moving the turntable is rotating. When the play head stops I've stopped the turntable. The timecode also shows as fairly stable in the meter (green/yellow circle). I'm so close !

I think it may has something to do with needing to converting the readings to Angular Velocity ?

Any ideas ?

ARDUINO:

#include "Adafruit_MLX90393.h"

Adafruit_MLX90393 sensor = Adafruit_MLX90393();
#define MLX90393_CS 10

void setup(void)
{
  Serial.begin(115200);

  /* Wait for serial on USB platforms. */
  while (!Serial) {
      delay(10);
  }

  Serial.println("Starting Adafruit MLX90393 Demo");

  if (! sensor.begin_I2C(0x18, &Wire1)) { // hardware I2C mode, can pass in address & alt Wire
    Serial.println("No sensor found ... check your wiring?");
    while (1) { delay(10); }
  }
  Serial.println("Found a MLX90393 sensor");

  sensor.setGain(MLX90393_GAIN_3X);
  Serial.print("Gain set to: ");
  switch (sensor.getGain()) {
    case MLX90393_GAIN_1X: Serial.println("1 x"); break;
    case MLX90393_GAIN_1_33X: Serial.println("1.33 x"); break;
    case MLX90393_GAIN_1_67X: Serial.println("1.67 x"); break;
    case MLX90393_GAIN_2X: Serial.println("2 x"); break;
    case MLX90393_GAIN_2_5X: Serial.println("2.5 x"); break;
    case MLX90393_GAIN_3X: Serial.println("3 x"); break;
    case MLX90393_GAIN_4X: Serial.println("4 x"); break;
    case MLX90393_GAIN_5X: Serial.println("5 x"); break;
  }

  sensor.setResolution(MLX90393_X, MLX90393_RES_17);
  sensor.setResolution(MLX90393_Y, MLX90393_RES_17);
  sensor.setResolution(MLX90393_Z, MLX90393_RES_16);

  sensor.setOversampling(MLX90393_OSR_3);
  sensor.setFilter(MLX90393_FILTER_5);
}

void loop(void) {
  float x, y, z;

  if (sensor.readData(&x, &y, &z)) {
      Serial.println(z, 4); // Output only Z value with 4 decimal places
  } else {
      Serial.println("0"); // Output 0 if reading fails
  }

  delay(5);
}

PYTHON:

import serial
import sounddevice as sd
import numpy as np
import time
import threading
import sys

# Constants
N = 1800          # Number of timecode cycles per revolution
SAMPLERATE = 44100  # Audio sample rate in Hz

# Global variables for phase and speed estimation
phi_last = 0.0      # Last computed phase (radians)
t_last = time.time() # Time of last update (seconds)
omega_est = 0.0     # Estimated angular velocity (radians/second)
theta_last = None   # Last received angle (radians)

# Set up serial port (adjust port name as needed)
try:
    ser = serial.Serial('/dev/cu.usbmodem64E83369BAEC2', 115200, timeout=1)
except serial.SerialException:
    print("Serial port not found. Check connection and port name.")
    sys.exit(1)

# Audio callback function to generate timecode signal
def callback(outdata, frames, time, status):
    global phi_last, t_last, omega_est
    # Time when the first sample will be played
    t_now = time.outputBufferDacTime
    delta_t = t_now - t_last
    # Current phase based on last known phase and estimated speed
    phi = phi_last + N * omega_est * delta_t
    # Phase increment per audio sample
    delta_phi = N * omega_est / SAMPLERATE
    # Generate stereo samples
    for i in range(frames):
        outdata[i, 0] = np.sin(phi)  # Left channel
        outdata[i, 1] = np.cos(phi)  # Right channel (90° phase shift)
        phi += delta_phi

# Serial reader thread function
def serial_reader():
    global phi_last, t_last, omega_est, theta_last
    while True:
        line = ser.readline().decode().strip()
        if line:
            try:
                theta = float(line)
                t_now = time.time()
                if theta_last is not None:
                    dt = t_now - t_last
                    # Avoid division by zero or unrealistic speeds
                    if dt > 0.001:
                        omega_est = (theta - theta_last) / dt
                        phi_last = N * theta
                        t_last = t_now
                        theta_last = theta
                else:
                    # Initialize on first reading
                    theta_last = theta
                    t_last = t_now
            except ValueError:
                # Skip malformed data
                continue

# Find Blackhole audio device
devices = sd.query_devices()
device_index = None
for dev in devices:
    if 'External Headphones' in dev['name']:
        device_index = dev['index']
        break
if device_index is None:
    print("External Headphones device not found.")
    sys.exit(1)

# Start the serial reader thread
thread = threading.Thread(target=serial_reader)
thread.daemon = True  # Thread exits when main program does
thread.start()

# Start audio stream
try:
    with sd.OutputStream(samplerate=SAMPLERATE, channels=2, callback=callback, device=device_index):
        print("Audio stream started. Sending timecode to External Headphones.")
        # Keep the script running
        while True:
            time.sleep(1)
except Exception as e:
    print(f"Error starting audio stream: {e}")
    sys.exit(1)

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.