Author Topic: AB Repeat Toggle (Hotkey)  (Read 2696 times)

Mayibongwe

  • Sr. Member
  • ****
  • Posts: 1733
  • Heal The World
Download it from the add-on link here

NB: I'm yet to address one aspect of the plugin that'll currently prevent you from setting up the AB Repeat for the last played song from the previous MB session.

I initially planned to add this function to the other hotkey plugin I shared a couple of weeks ago, but I've decided otherwise.
Splitting them up will help with discoverability and ensure users get to download only the features they desire.

The function was requested here and I've been enjoying it myself too:
It would be great if AB repeat functionality could be added to Musicbee.




The entire code for anyone interested in taking a peak.
Code
using System;
using System.IO;
using System.Timers;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace MusicBeePlugin
{
    public partial class Plugin
    {
MusicBeeApiInterface mbApi;
System.Timers.Timer myTimer = new Timer();
ToolTip status = new ToolTip();

string dataFolder;
string logPath;

bool timer_Active = false;
bool pauseOn = false;

int mode = 0;
int start_time;
int end_time;
int streamHandle = -1;
double position;

        public PluginInfo Initialise(IntPtr apiInterfacePtr)
        {
            mbApi = new MusicBeeApiInterface();
            PluginInfo info = new PluginInfo();

            mbApi.Initialise(apiInterfacePtr);
            info.PluginInfoVersion = PluginInfoVersion;
            info.Name = "AB_Repeat Hotkey Toggle";
            info.Description = "Triggers a repeat state between two points in a song";
            info.Author = "Mayibongwe (2024)";
            info.TargetApplication = "";
            info.Type = PluginType.General;
            info.VersionMajor = 1;
            info.VersionMinor = 3;
            info.Revision = 0;
            info.MinInterfaceVersion = MinInterfaceVersion;
            info.MinApiRevision = MinApiRevision;
            info.ReceiveNotifications = ReceiveNotificationFlags.PlayerEvents | ReceiveNotificationFlags.TagEvents;
            info.ConfigurationPanelHeight = -1;

            dataFolder = Path.Combine(mbApi.Setting_GetPersistentStoragePath(), "mb_RepeatAB");
            if (Directory.Exists(dataFolder) == false) Directory.CreateDirectory(dataFolder);
            logPath = Path.Combine(dataFolder, "Activity.log");

            return info;
        }

public bool Configure(IntPtr panelHandle)
        {
            return false;
        }

public void SaveSettings(){}

public void Close(PluginCloseReason reason)
{
            myTimer.Dispose();
            status.Dispose();
}

public void Uninstall()
{
            if (Directory.Exists(dataFolder)) Directory.Delete(dataFolder, true);
}

public void writeToLog(string data)
{
            File.AppendAllText(logPath, data + Environment.NewLine);
}

public void ReceiveNotification(string sourceFileUrl, NotificationType type)
        {
switch(type)
{
case NotificationType.PluginStartup:
File.WriteAllText(logPath, "Plugin in effect" + Environment.NewLine);
mbApi.MB_AddMenuItem(null, "Player: Toggle AB Repeat", repeatHandler);
break;

case NotificationType.TrackChanged:
if(end_time != 0)
{
resetPoints();
writeToLog("Repeat off / New playing track");
}
break;

case NotificationType.PlayStateChanged:
if(end_time != 0)
{
PlayState status = mbApi.Player_GetPlayState();

if(timer_Active == true)
{
mode = 0;
if(status == PlayState.Paused)
{
pauseOn = true;
writeToLog("Playback paused");
}

if(status == PlayState.Stopped) writeToLog("Playback stopped");
}
else
{
if(status == PlayState.Playing && pauseOn == true)
{
mode = 2;
pauseOn = false;
myTimer.Enabled = true;
writeToLog("Playback resumed");
}
}
}
break;
}
        }

public void repeatHandler(object sender, EventArgs e)
{
if(streamHandle != -1 && mbApi.Player_GetPlayState() != PlayState.Stopped)
{
mode += 1;
switch(mode)
{
case 1:
start_time = mbApi.Player_GetPosition();
writeToLog("Start Time: " + start_time);
break;

case 2:
end_time = mbApi.Player_GetPosition();

if(end_time < start_time)
{
writeToLog("End Time Invalid: " + end_time);
resetPoints();
writeToLog("AB points captured discarded: Retry");
}
else
{
writeToLog("End Time: " + end_time);
repeatTrack();
showTooltip("on");
}
break;

case 3:
resetPoints();
writeToLog("Repeat turned off");
showTooltip("off");
break;
}
}
else
{
writeToLog("Cannot proceed without stream handle");
showTooltip("handle");
}
}

private void showTooltip(string enabled)
{
if(enabled == "on") enabled = "AB Repeat On";
else if (enabled == "off") enabled = "AB Repeat Off";
else enabled = "Missing Stream Handle";

status.Show(enabled, Application.OpenForms[0], Cursor.Position.X-42, Cursor.Position.Y-25, 1750);
}

private void resetPoints()
{
mode = 0;
start_time = 0;
end_time = 0;
}

private void repeatTrack()
{
switch(mbApi.Player_GetPlayState())
{
case PlayState.Playing:
mbApi.Player_SetPosition(start_time);

myTimer = new System.Timers.Timer();
myTimer.Interval = 1;
myTimer.Enabled = true;
myTimer.Elapsed += timer_Tick;
writeToLog("AB Repeat Commenced: While Playing");
break;

case PlayState.Paused:
pauseOn = true;
mbApi.Player_SetPosition(start_time);
BASS_ChannelPause(streamHandle);
writeToLog("AB Repeat Commenced: While Paused");
break;
}
}

private void timer_Tick(object sender, ElapsedEventArgs e)
{
if(mode == 2)
{
timer_Active = true;
position = toSeconds(BASS_ChannelGetPosition(streamHandle, 0));

if(position > end_time || position < start_time)
{
writeToLog("Loop ended: " + position);
mbApi.Player_SetPosition(start_time);
}
}
else
{
timer_Active = false;
writeToLog("Timing disabled");
myTimer.Enabled = false; // stop timing if repeat is disabled
}
}

private double toSeconds(long byteData)
{
double seconds = BASS_ChannelBytes2Seconds(streamHandle, byteData);
return seconds*1000;
}

public void InitialiseStream(int handle)
{
streamHandle = handle;
writeToLog("Stream Initialised: " + handle);
}

[System.Security.SuppressUnmanagedCodeSecurity()]
        [DllImport("bass.dll", CharSet = CharSet.Auto)]
public static extern long BASS_ChannelGetPosition(int handle, int mode);

[System.Security.SuppressUnmanagedCodeSecurity()]
        [DllImport("bass.dll", CharSet = CharSet.Auto)]
public static extern bool BASS_ChannelPause(int handle);

[System.Security.SuppressUnmanagedCodeSecurity()]
        [DllImport("bass.dll", CharSet = CharSet.Auto)]
public static extern double BASS_ChannelBytes2Seconds(int handle, long byteData);
    }
}
Last Edit: September 14, 2024, 04:48:02 PM by Mayibongwe
Strength and Honour (2025)

Mayibongwe

  • Sr. Member
  • ****
  • Posts: 1733
  • Heal The World
After more testing, I've released an updated version with better optimization and a few error handlers here and there.
Strength and Honour (2025)

monkey

  • Full Member
  • ***
  • Posts: 148
very good idea
it runs very well for me (with visual markers on the wave bar it would be top ... but even like this  it's really cool...)
thanks

Mayibongwe

  • Sr. Member
  • ****
  • Posts: 1733
  • Heal The World
I've added a tooltip as a visual indication of when this repeat mode is turned on/off.
Strength and Honour (2025)

monkey

  • Full Member
  • ***
  • Posts: 148
i found the possibility to make a button in the toolbar and it's really cool
but i don't find the visual indication of when this repeat mode is turned on/off ?
could you explain please ?

Mayibongwe

  • Sr. Member
  • ****
  • Posts: 1733
  • Heal The World
The hotkey currently has 3 cycles where:

- the 1st call/click/press sets the start time
- the 2nd call sets the end time (at this point, a tooltip showing "AB Repeat On" should flash near your mouse pointer for nearly 2 seconds)
- the 3rd call turns off the AB repeat (a tooltip showing "AB Repeat Off" should appear for the same duration near your mouse pointer)

Edit: Something like this
Last Edit: September 14, 2024, 02:59:14 PM by Mayibongwe
Strength and Honour (2025)

monkey

  • Full Member
  • ***
  • Posts: 148
thanks ! Now i see it but it isn't nearby the mouse pointer (i'm using a touchpad)
it is in the left top corner
is there something to do to have it, nearby the mouse pointer ?

Mayibongwe

  • Sr. Member
  • ****
  • Posts: 1733
  • Heal The World
Now i see it but it isn't nearby the mouse pointer (i'm using a touchpad)
I've now explicitly set it to appear wherever the cursor is (despite it oddly having behaved that way on my machine already).
Redownload the plugin and see if it works well for you too now.
Strength and Honour (2025)

monkey

  • Full Member
  • ***
  • Posts: 148
now it's ok !  thanks !
just an idea, i don't know if it's possible and if it's a better solution
would it be possible to display rather on the wavebar nearby the player cursor ?

Mayibongwe

  • Sr. Member
  • ****
  • Posts: 1733
  • Heal The World
would it be possible to display rather on the wavebar nearby the player cursor?
A proper solution for this would be complicated, I fear (unless I asked Steven to provide access to the control that houses the player panel).
As it stands, I only have the means to position the tooltip relative to the main MusicBee window.

If I displayed the tooltip somewhere at the bottom of the MB window (working on the assumption that that's where the player panel normally is),
it would work against those who have their player panel positioned at the top.
 
Perhaps this suggestion by hiccup would be the best way to go: https://getmusicbee.com/forum/index.php?topic=1972.msg224518#msg224518
Let me see if I can bump that request - not suggesting you hold your breath though as this is very low priority on all ends.
Strength and Honour (2025)

monkey

  • Full Member
  • ***
  • Posts: 148
i understand that it's not so easy, and anyway it's just an idea (and perhaps not so a good idea... the decision depends on you and Steven)
i think this plugin is a good idea, and if you have the possibility to improve it in one way or other, it would be great !
 :D  :D  :D