Lyrics Page Layout Similar to ProPresenter7 Stage Layout

This is awesome! Im having a hard time understanding how to get the JS code working on my computer. I have the js file uploaded in the same directory as the styles folder, but im unsure how to get it working.

What is the obvious thing im missing haha?

Make sure you add the textFit.js file to the directory and then update the js file already in the folder with Leo’s custom script.

I got it to work! Thank you for the help.

I don’t think I got it 100% however. Like mentioned before by @leolabs, switching back and forth resets the lyrics page and makes it all go back to normal size etc.

When it is fully functional, everything is slightly over to the right side as well. Will post examples soon

This is a video I took to show you guys what’s happening. Exiting the lyrics page resets it and makes it not formatted correctly. But going back and saving the scripts file starts the script again and it fixes itself. However it is still slightly towards the bottom right corner without the proper spacing there.

There are still occasions where if I update lyrics in the middle of the song, it won’t resize but refreshing the page always fixes it for me. When I get near my machine, I will send everything I have in mine so you can compare.

Hey @CodyWright,

I just made a few adjustments to the script so it can now handle navigating between pages:

let observer;

const init = () => {
  const target = document.querySelector(".lyrics-page .lyrics div");

  if (!target) {
    console.log("This doesn't seem to be a lyrics page");
    return;
  }

  const callback = async (mutationList) => {
    const lyrics = document.querySelectorAll(".lyrics-line span");
    const { textFit } = await import("./textFit.js");

    lyrics.forEach((el) => {
      el.style.display = "block";
      el.style.width = el.parentElement.clientWidth + "px";
      el.style.height = el.parentElement.clientHeight + "px";
    });

    // maxFontSize can be adjusted if the font becomes too large
    textFit(lyrics, { multiLine: true, maxFontSize: 300 });
  };

  observer = new MutationObserver(callback);
  observer.observe(target, { childList: true });
  callback();

  // Since AbleSet 2.4.0, you have access to real-time updates
  ableset.getSocket("lyrics").on("tracksUpdated", () => {
    setTimeout(callback, 1000);
  });
};

// Keep track of the current URL so we can run
// the script again when navigating to a lyrics page
let lastLocation = "";

setInterval(() => {
  if (location.href !== lastLocation && location.href.includes("/lyrics")) {
    lastLocation = location.href;
    observer?.disconnect();
    init();
  }
}, 1000);

Could you check if this works on your end?

I love seeing socket integration into the script! Thanks for making this possible for us Leo!

Do you think the textfit import would need moved up to to the top of the code? It currently appears to be re-imported on every page refresh.

Currently this isn’t possible because the script itself isn’t tagged as a module, so top-level import statements aren’t available, but I could change that in the next version :slight_smile:

I wouldn’t worry about the import running multiple times though – those requests are most likely cached so it shouldn’t have any impact on performance.

The latest beta, AbleSet 2.5.0-beta.1, now supports top-level imports in the custom script. Starting with this version, you can use the following syntax to import modules:

import { textFit } from "./textFit.js";

let observer;

const init = () => {
  const target = document.querySelector(".lyrics-page .lyrics div");

  if (!target) {
    console.log("This doesn't seem to be a lyrics page");
    return;
  }

  const callback = async (mutationList) => {
    const lyrics = document.querySelectorAll(".lyrics-line span");

    lyrics.forEach((el) => {
      el.style.display = "block";
      el.style.width = el.parentElement.clientWidth + "px";
      el.style.height = el.parentElement.clientHeight + "px";
    });

    // maxFontSize can be adjusted if the font becomes too large
    textFit(lyrics, { multiLine: true, maxFontSize: 300 });
  };

  observer = new MutationObserver(callback);
  observer.observe(target, { childList: true });
  callback();

  // Since AbleSet 2.4.0, you have access to real-time updates
  ableset.getSocket("lyrics").on("tracksUpdated", () => {
    setTimeout(callback, 1000);
  });
};

// Keep track of the current URL so we can run
// the script again when navigating to a lyrics page
let lastLocation = "";

setInterval(() => {
  if (location.href !== lastLocation && location.href.includes("/lyrics")) {
    lastLocation = location.href;
    observer?.disconnect();
    init();
  }
}, 1000);
1 Like

That’s awesome Leo! This will also allow me to clean up the code for loading my custom plugin for Ableset

1 Like

Leo continues to amaze again and again!

This works incredibly. It is centered. For longer lines, it looks like there is zero padding between the text and the end of the screen. It might be nice to add a very small pad on the left and right sides of the screen.

Another thing… Is it possible for Ableset to remember which lyric page you selected when you tab back and forth? Right now, it just selected which ever one is first every time. This isn’t a huge deal, but it is annoying sometimes to have to reselect it if I tab out for a second to check something, etc.

I also like how Ableset scrolls along with it right now, but in ProPresenter it does not scroll and I want to make the transition as seamless as possible for the team. (hopefully they don’t even know until I tell them haha)

Is there a tag modifier to stop the auto-scroll? but instead jump between lyrics almost like frames?

It also could be a good option to allow you to target named lyrics tracks with tags within the CSS script or JS script so that the lyric page in Ableton session doesn’t need it, making it ever so much cleaner. So any track with “+LYRICS” called “Confidence-Monitor” automatically receives the [nozoom] [top] [nofade]

1 Like

Hey @CodyWright,

I just released AbleSet 2.5.0-beta.2 which remembers the most recent lyrics track when you switch between views.

To add some padding to your lines, you should be able to use the following CSS:

.lyrics .lyrics-line {
  padding: 0 10px; /* change to taste */
}

Some of the lyrics attributes are just styles, so you can add them to your custom CSS to target specific lines, e.g.

.lyrics.confidence-monitor .lyrics-line, 
.lyrics.confidence-monitor .image-line {
  /* disable the zoom */
  transform: scale(1);
}

.lyrics.confidence-monitor {
  /* disable the fade */
  --fade-top: 1 !important;
  --fade-bottom: 1 !important;
}

The [top] attribute changes the scroll behavior which unfortunately can’t be overridden with CSS.

This is currently not possible, but it shouldn’t be too hard to implement. I’ll add it to my todo list.

2 Likes

I am having an issue with the text scaling. It often times (especially when making changes to the lyrics or to any of the “Lyrics” tracks) changes back to the original size (much smaller than the scaled text), and needs to be refreshed each time this happens otherwise the text is too small to see on our Stage Display. This has always been the case since using Textfit.js and while it is annoying to not be able to make changes on the fly without the text refreshing and randomly changing to sizes much larger than the original or much smaller, it is manageable.

But what it has started doing (honestly, I don’t remember what version it started on), is reverting back to the original text size between each song and if myself or our sound engineer do not catch it, there is a good bit of time that goes by that the lyrics are very difficult to read.

I know this is a weird and unique setup but was just curious if there was a possible fix for this?

Hey @iamderkis,

I just made a small modification to the code that checks the lines every second and if it notices that a line hasn’t been adjusted to the screen yet, it runs the script again. Here’s the new code:

import { textFit } from "./textFit.js";

let observer;
let interval;

const init = () => {
  const target = document.querySelector(".lyrics-page .lyrics div");

  if (!target) {
    console.log("This doesn't seem to be a lyrics page");
    return;
  }

  const callback = async (_a, _b, options) => {
    const lyrics = document.querySelectorAll(".lyrics-line span");

    lyrics.forEach((el) => {
      el.style.display = "block";
      el.style.width = el.parentElement.clientWidth + "px";
      el.style.height = el.parentElement.clientHeight + "px";
    });

    // maxFontSize can be adjusted if the font becomes too large
    textFit(lyrics, { multiLine: true, maxFontSize: 300, ...options });
  };

  observer = new MutationObserver(callback);
  observer.observe(target, { childList: true });
  callback();

  // Since AbleSet 2.4.0, you have access to real-time updates
  ableset.getSocket("lyrics").on("tracksUpdated", () => {
    setTimeout(callback, 1000);
  });

  if (interval) {
    clearInterval(interval);
  }

  // If a line is replaced, re-fit its text.
  // This is a bit of a brute-force approach that just runs the fitting
  // process every second and ignores lines that have already been adjusted.
  setInterval(() => callback(null, null, { reProcess: false }), 1000);
};

// Keep track of the current URL so we can run
// the script again when navigating to a lyrics page
let lastLocation = "";

setInterval(() => {
  if (location.href !== lastLocation && location.href.includes("/lyrics")) {
    lastLocation = location.href;
    observer?.disconnect();
    init();
  }
}, 1000);

Let me know if this works for you :slight_smile:

I am attaching a screen recording of what is happening. It does fix the problem of the text getting smaller in between songs and increases the size of that text but a weird issue is occurring as it in “debouncing”.

Oh, that’s not looking good! Does this version work better?

import { textFit } from "./textFit.js";

let observer;
let interval;

const init = () => {
  const target = document.querySelector(".lyrics-page .lyrics div");

  if (!target) {
    console.log("This doesn't seem to be a lyrics page");
    return;
  }

  const callback = async (_a, _b, options) => {
    const lyrics = document.querySelectorAll(
      ".lyrics-line span:not(.text-fit)"
    );

    lyrics.forEach((el) => {
      el.style.display = "block";
      el.style.width = el.parentElement.clientWidth + "px";
      el.style.height = el.parentElement.clientHeight + "px";
      el.classList.add("text-fit");
    });

    // maxFontSize can be adjusted if the font becomes too large
    textFit(lyrics, { multiLine: true, maxFontSize: 300, ...options });
  };

  observer = new MutationObserver(callback);
  observer.observe(target, { childList: true });
  callback();

  // Since AbleSet 2.4.0, you have access to real-time updates
  ableset.getSocket("lyrics").on("tracksUpdated", () => {
    setTimeout(callback, 1000);
  });

  if (interval) {
    clearInterval(interval);
  }

  // If a line is replaced, re-fit its text.
  // This is a bit of a brute-force approach that just runs the fitting
  // process every second and ignores lines that have already been adjusted.
  interval = setInterval(() => callback(null, null, { reProcess: false }), 1000);
};

// Keep track of the current URL so we can run
// the script again when navigating to a lyrics page
let lastLocation = "";

setInterval(() => {
  if (location.href !== lastLocation && location.href.includes("/lyrics")) {
    lastLocation = location.href;
    observer?.disconnect();
    init();
  }
}, 1000);
1 Like

That works brilliantly! As always, thank you so much for your help!

1 Like

An issue I have run into is that over time (usually about 2 or 3 minutes) the page begins to lag quite a bit when swapping lyrics and then goes unresponsive. I reverted back to the previous script and it worked fine so something is bogging down. As far as sizing between songs, it works brilliantly, the only issue is that it bogs down over time until the lyrics stop progressing. Also if I load the lyrics page and it sits without running the tracks, it will do the same thing so it seems to be happening the moment the page is loaded.

Hey @iamderkis,

I’ve just made some adjustments to the code in my last post. Could you try if this works better?

It’s still bogging down over time. It’s creating these elements over and over again (screenshot attached) and then it starts bogging down.