mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
* Added base type for UI panels of MIDI input sources. * Renamed BandMenu into BandMidiSource. * Implemented the UI overhaul, skipping channels dialog functionality for now. * Refactored for conventions and reordered type members. Got rid of SetInstrument/ClearInstrument again. * Decreased background opacity for UnavailableOverlay to improve readability. * Localization * Added text to InputMidiSource so players know what it does. * Changed window MinSize to SetSize. Allows players to reduce window size if desired. * Improved button looks by adding style classes. * cleanup * leftover cleanup * Reverted fetching InstrumentSystem via IoCManager (crashes it, woops). * added filter bar to file list * refactored almost all system/manager interaction into instrument BUI * text changed for instruments-component-menu-input-notice-label * fixed InstrumentBoundUserInterface.Dispose not unsubscribing from everything * minor cleanup * finished configuration menu & cleanup * Comments and fixes for InstrumentMidiSourceBase * Fixed RA0051 * Removed unncecessary null check * Added methods for accessing the selected track. * Limited depth of search for instrument master to prevent deadlock. * Cached system access * Storing midis asynchronously * Actual MIDI data now only loaded before playback. * async removed from InstrumentBoundUserInterface.LoadStoredUserMidis * Replaced GetComponent calls with TryGetComponent * Localized MIDI playback track timer * Searching for next track using raw index now * Fixed CS0168 * XML comments, minor fixes and disabled MIDI library management while a track is playing. * removed timers with simple watchdog and switched to MidiFileCollectionManager * Missed one (timer) * Changed access for MidiFileCollectionManager.UserMidiDirectory field to public. * Added instrument config control for MIDI collection utilities. * redesigned instrument config menu * Removed rename function, updated adding implementation * more redesigns * fixed _isMidiFileDialogueWindowOpen not resetting correctly in certain cases. * file scoped namespaces * replaced EntityUid with Entity<InstrumentComponent> * if spaces * returning Tooltip instead of Control * replaced SetEntity with public field * readonly system fields * InstrumentMenu MinSize * xml comment for InstrumentMidiSourceBase * small refactor * more cleanup * TryGetComponent to GetComponent on Open() * more formatting issues * switched to [Dependency] for systems * playback improvements * disable playback button if no file selected and playing * activating shuffle now selects track if none is selected * rearranged playback buttons * channels overhauled * Play/Stop label to Play only * Removed all channel on/off * Program now default channel name display selection * improved RemoveButton.Disable behavior * channels now toggable by clicking anywhere * Shuffle no longer selects already selected track. * Loop & Shuffle no longer both selectable at once * minor improvements to channel item visuals * minor channel toggle fixes * xml docs & code cleanup * `RefreshBandRequest` now sent automatically when band source becomes visible * converted 5 items tuple to MidiChannelInfo record * added markup support to instrument menu configuration header * added note to midi channels config header explaining update behavior.
201 lines
6.6 KiB
C#
201 lines
6.6 KiB
C#
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using JetBrains.Annotations;
|
|
using Robust.Shared.ContentPack;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Client.Audio.Midi;
|
|
|
|
/// <summary>
|
|
/// Handles storage/management of MIDI files stored inside the user data directory.
|
|
/// </summary>
|
|
[PublicAPI]
|
|
public sealed partial class MidiFileCollectionManager : IPostInjectInit
|
|
{
|
|
/// <summary>
|
|
/// Directory path to use inside UserData for storing MIDIs.
|
|
/// </summary>
|
|
public static readonly ResPath UserMidiDirectory = new("/UserMidis/");
|
|
|
|
private const string SawmillCategory = "midifilecollection";
|
|
|
|
[Dependency] private IResourceManager _resManager = default!;
|
|
[Dependency] private ILogManager _logManager = default!;
|
|
|
|
private ISawmill _sawmill = default!;
|
|
|
|
private readonly List<ResPath> _filePaths = [];
|
|
|
|
/// <summary>
|
|
/// Raised after a MIDI file has been added to the library. Contains added file path as argument.
|
|
/// </summary>
|
|
public event Action<ResPath>? MidiFileAdded;
|
|
|
|
/// <summary>
|
|
/// Raised after a MIDI file has been removed from the library. Contains removed file path as argument.
|
|
/// </summary>
|
|
public event Action<ResPath>? MidiFileRemoved;
|
|
|
|
/// <summary>
|
|
/// Raised after more than one or two library changes occured at once. (i.e. initial load or when all items deleted)
|
|
/// </summary>
|
|
public event Action? MidiFilesReset;
|
|
|
|
///<inheritdoc cref="IPostInjectInit"/>
|
|
public void PostInject()
|
|
{
|
|
EnsureMidiDirectoryExists();
|
|
ReloadLibrary();
|
|
_sawmill = _logManager.GetSawmill(SawmillCategory);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the binary content of the given MIDI file.
|
|
/// </summary>
|
|
/// <param name="filePath">MIDI file path to get.</param>
|
|
/// <returns>MIDI binary as a byte array or an empty byte array if the file doesn't exist.</returns>
|
|
public byte[] GetMidiData(ResPath filePath)
|
|
{
|
|
try
|
|
{
|
|
var fullPath = UserMidiDirectory / filePath;
|
|
return _resManager.UserData.ReadAllBytes(fullPath);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_sawmill.Error($"Failed to read MIDI data from '{filePath}': {e.Message}");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an enumeration of all available MIDI files.
|
|
/// </summary>
|
|
/// <returns>An enumeration of MIDI file paths.</returns>
|
|
public IEnumerable<ResPath> GetMidiFiles()
|
|
{
|
|
return _filePaths;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores the given byte stream with the given file path inside the <see cref="UserMidiDirectory"/> directory.
|
|
/// </summary>
|
|
/// <param name="filePath">File path to write.</param>
|
|
/// <param name="data">Binary data to write.</param>
|
|
/// <remarks>Raises <see cref="MidiFileAdded"/> on success.</remarks>
|
|
public async Task<bool> AddMidiFile(ResPath filePath, Stream data)
|
|
{
|
|
try
|
|
{
|
|
await using var file = _resManager.UserData.OpenWrite(UserMidiDirectory / filePath);
|
|
await data.CopyToAsync(file);
|
|
_filePaths.Add(filePath);
|
|
MidiFileAdded?.Invoke(filePath);
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_sawmill.Error($"Failed to store MIDI file '{filePath}' in library: {e.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores the given byte array with the given file path inside the <see cref="UserMidiDirectory"/> directory.
|
|
/// </summary>
|
|
/// <param name="filePath">File path to write.</param>
|
|
/// <param name="data">Binary data to write.</param>
|
|
/// <remarks>Raises <see cref="MidiFileAdded"/> on success.</remarks>
|
|
public async Task<bool> AddMidiFile(ResPath filePath, byte[] data)
|
|
{
|
|
return await AddMidiFile(filePath, new MemoryStream(data));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renames a MIDI file inside the library.
|
|
/// </summary>
|
|
/// <param name="oldPath">Current file path</param>
|
|
/// <param name="newPath">New file path</param>
|
|
/// <remarks>Raises <see cref="MidiFileRemoved"/> and <see cref="MidiFileAdded"/> on success.</remarks>
|
|
public bool RenameMidiFile(ResPath oldPath, ResPath newPath)
|
|
{
|
|
try
|
|
{
|
|
var fullOldPath = UserMidiDirectory / oldPath;
|
|
var fullNewPath = UserMidiDirectory / newPath;
|
|
fullOldPath = fullOldPath.Clean();
|
|
fullNewPath = fullNewPath.Clean();
|
|
_resManager.UserData.Rename(fullOldPath, fullNewPath);
|
|
_filePaths.Remove(oldPath);
|
|
MidiFileRemoved?.Invoke(oldPath);
|
|
_filePaths.Add(newPath);
|
|
MidiFileAdded?.Invoke(newPath);
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_sawmill.Error($"Failed to rename MIDI file '{oldPath}' with '{newPath}': {e.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Permanently removes the given MIDI file if it exists inside <see cref="UserMidiDirectory"/>.
|
|
/// </summary>
|
|
/// <param name="filePath">File path to remove.</param>
|
|
/// <remarks>Raises <see cref="MidiFileRemoved"/></remarks>
|
|
public void RemoveMidiFile(ResPath filePath)
|
|
{
|
|
DeleteMidiFile(filePath);
|
|
_filePaths.Remove(filePath);
|
|
MidiFileRemoved?.Invoke(filePath);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all registered MIDI files, permanently.
|
|
/// </summary>
|
|
/// <remarks>Raises <see cref="MidiFilesReset"/></remarks>
|
|
public void RemoveAllMidiFiles()
|
|
{
|
|
foreach (var path in _filePaths)
|
|
{
|
|
DeleteMidiFile(path);
|
|
}
|
|
|
|
_filePaths.Clear();
|
|
MidiFilesReset?.Invoke();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears and reloads the entire MIDI library.
|
|
/// </summary>
|
|
public void ReloadLibrary()
|
|
{
|
|
_filePaths.Clear();
|
|
if (!_resManager.UserData.IsDir(UserMidiDirectory))
|
|
return;
|
|
|
|
foreach (var path in _resManager.UserData.DirectoryEntries(UserMidiDirectory))
|
|
{
|
|
var filePath = new ResPath(UserMidiDirectory + path);
|
|
if (!filePath.Extension.Equals("midi") && !filePath.Extension.Equals("mid"))
|
|
continue;
|
|
|
|
_filePaths.Add(new ResPath(path));
|
|
}
|
|
|
|
MidiFilesReset?.Invoke();
|
|
}
|
|
|
|
private void DeleteMidiFile(ResPath filePath)
|
|
{
|
|
var path = (UserMidiDirectory / filePath).Clean();
|
|
_resManager.UserData.Delete(path);
|
|
}
|
|
|
|
private void EnsureMidiDirectoryExists()
|
|
{
|
|
if (!_resManager.UserData.Exists(UserMidiDirectory))
|
|
_resManager.UserData.CreateDir(UserMidiDirectory);
|
|
}
|
|
}
|