diff --git a/Documentation/features.html b/Documentation/features.html
index 06ecbf0c..3c581a4a 100644
--- a/Documentation/features.html
+++ b/Documentation/features.html
@@ -25,7 +25,7 @@
diff --git a/Format_Descriptions/Ben Daglish.txt b/Format_Descriptions/Ben Daglish.txt
new file mode 100644
index 00000000..c9ccc069
--- /dev/null
+++ b/Format_Descriptions/Ben Daglish.txt
@@ -0,0 +1,123 @@
+VoicePlaybackInfo
+
+ 0 2 DMA bit
+ 2 2 Interrupt bit
+ 4 2 Hardware register offset
+ 6 2 Delay counter for when to set loop
+ 8 2 Note period
+ A 2 Portamento add value
+ C 2 Final volume
+ E 2 Final volume slide speed
+10 2 Final volume slide increment value
+12 2 Final volume slide speed counter
+14 2 Ticks to play sample
+16 2 Instrument portamento duration
+18 2 Instrument portamento increment value
+1A 2 Instrument vibrato depth
+1C 2 Instrument period add value
+1E 2 Instrument vibrato increment value
+20 4 Pointer to code to set e.g. sample loop
+24 4 Pointer to instrument info
+
+
+VoiceInfo
+
+ 0 2 This voice index into instrument mapping table. Are initialized with these values: 0, 8, 10, 18
+ 2 4 Pointer to next byte in position list
+ 6 4 Pointer to start of track data
+ A 4 Pointer to next byte in track data
+ E 4 Pointer to instrument info
+12 1 Track loop counter
+13 1 Transpose
+14 1 If true, switch to next position or repeat track
+15 1 Ticks left before reading next track command
+16 1 Channel is enabled
+17 1 Transposed note
+18 1 Use new note
+
+1A 4 Pointer to instrument info
+1E 1 Previous transposed note
+1F 1 Portamento 1 enabled
+20 1 Portamento start delay
+21 1 Portamento duration
+22 1 Portamento delta node number
+23 1 Portamento control flag
+24 1 Portamento start delay counter
+25 1 Portamento duration counter
+26 4 Portamento value to add to playing period
+2A 1 Portamento 2 enabled
+2B 1 Volume fade enabled
+2C 1 Volume fade init speed
+2D 1 Volume fade duration
+2E 2 Volume fade add value
+30 1 Volume fade running
+31 1 Volume fade speed
+32 1 Volume fade speed counter
+33 1 Volume fade duration counter
+34 2 Volume fade increment value
+36 2 Volume fade value
+38 2 Channel volume
+3A 2 Channel volume slide speed
+3C 2 Channel volume slide increment value
+3E 1 Control flag start value
+3F
+46 1 Control flag
+
+
+When playing a note, the player uses an instrument number set on the channel.
+This number is between 0 and 7. This number is then used to lookup in a
+mapping table to find the sample number to use. This mapping table is
+initialized to use the first 8 samples.
+
+
+Position list
+
+V1 player:
+
+00-7F = Track number
+80-BF = Loop track by & 1F
+C0-C7 xx = Update instrument mapping table at index & 7 to sample xx
+FE xx = Transpose
+FF = End of position list
+
+V2 player:
+
+00-C7 = Track number
+C8-EF = Loop track by & 1F
+F0-F7 xx = Update instrument mapping table at index - F0 to sample xx
+FD xx = Start master volume fade. xx = fade speed
+FE xx = Transpose
+FF = End of position list
+
+
+Track data
+
+00-7E xx yy = Note. xx is how many ticks to wait until reading next track command. yy is only available on some players and only if xx = 0. yy is the same as xx, but the behaviour of SetupSample() changes
+7F xx = Wait. xx is how many ticks to wait until reading next track command
+80-88 = Use instrument by & 7
+89-BF = Set flag by & F
+C0 xx yy zz = Portamento 1. xx = start delay, yy = duration, zz = delta number of nodes
+C1 = Stop portamento 1
+C2 xx yy zz = Volume fade. xx = speed, yy = duration, zz = increment value
+C3 = Stop volume fade
+C4 xx = Portamento 2. xx = duration
+C5 = Stop portamento 2
+C6 xx yy zz = Global volume slide. xx = start value, yy = speed, zz = increment value
+C7 = Stop global volume slide
+FF = End of track data
+
+
+Instrument info:
+
+ 0 4 Offset to sample data
+ 4 4 Offset to sample data where loop starts
+ 8 2 Length in words (one shot)
+ A 2 Length of loop in words
+ C 2 Volume
+ E 2 Volume fade speed
+10 2 Portamento duration
+12 2 Portamento increment value
+14 2 Vibrato depth
+16 2 Vibrato increment value
+18 2 Note transpose
+1A 2 Fine tune Period
diff --git a/README.md b/README.md
index dc279dd3..f46906cc 100644
--- a/README.md
+++ b/README.md
@@ -50,6 +50,7 @@ Modules in all supported formats can be found on my homepage at https://nostalgi
| Asylum Music Format | .amf | | Xmp |
| Audio Sculpture | .adsc | | ModTracker |
| AudioIFF | .aiff / .aif | | Sample |
+| Ben Daglish | .bd | | Ben Daglish |
| Ben Replay | .ben | ProWizard | ModTracker |
| Binary Packer | .bnr | ProWizard | ModTracker |
| Channel Player 1 | .ch1 / .chn / .chan | ProWizard | ModTracker |
diff --git a/Source/Agents/Players/BenDaglish/BenDaglish.cs b/Source/Agents/Players/BenDaglish/BenDaglish.cs
new file mode 100644
index 00000000..aa05c582
--- /dev/null
+++ b/Source/Agents/Players/BenDaglish/BenDaglish.cs
@@ -0,0 +1,66 @@
+/******************************************************************************/
+/* This source, or parts thereof, may be used in any software as long the */
+/* license of NostalgicPlayer is keep. See the LICENSE file for more */
+/* information. */
+/******************************************************************************/
+using System;
+using System.Runtime.InteropServices;
+using Polycode.NostalgicPlayer.Kit.Bases;
+using Polycode.NostalgicPlayer.Kit.Containers;
+using Polycode.NostalgicPlayer.Kit.Interfaces;
+
+// This is needed to uniquely identify this agent
+[assembly: Guid("63EF8780-B326-4224-8216-F9EEE26C07A4")]
+
+namespace Polycode.NostalgicPlayer.Agent.Player.BenDaglish
+{
+ ///
+ /// NostalgicPlayer agent interface implementation
+ ///
+ public class BenDaglish : AgentBase
+ {
+ private static readonly Guid Agent1Id = Guid.Parse("CE362B0B-D910-4EAD-974E-A28D047EADDB");
+
+ #region IAgent implementation
+ /********************************************************************/
+ ///
+ /// Returns the name of this agent
+ ///
+ /********************************************************************/
+ public override string Name => Resources.IDS_BD_NAME;
+
+
+
+ /********************************************************************/
+ ///
+ /// Returns a description of this agent
+ ///
+ /********************************************************************/
+ public override string Description => Resources.IDS_BD_DESCRIPTION;
+
+
+
+ /********************************************************************/
+ ///
+ /// Returns all the formats/types this agent supports
+ ///
+ /********************************************************************/
+ public override AgentSupportInfo[] AgentInformation =>
+ [
+ new AgentSupportInfo(Resources.IDS_BD_NAME_AGENT1, Resources.IDS_BD_DESCRIPTION_AGENT1, Agent1Id)
+ ];
+
+
+
+ /********************************************************************/
+ ///
+ /// Creates a new worker instance
+ ///
+ /********************************************************************/
+ public override IAgentWorker CreateInstance(Guid typeId)
+ {
+ return new BenDaglishWorker();
+ }
+ #endregion
+ }
+}
diff --git a/Source/Agents/Players/BenDaglish/BenDaglish.csproj b/Source/Agents/Players/BenDaglish/BenDaglish.csproj
new file mode 100644
index 00000000..270ac02b
--- /dev/null
+++ b/Source/Agents/Players/BenDaglish/BenDaglish.csproj
@@ -0,0 +1,46 @@
+
+
+
+ net9.0
+ Polycode.NostalgicPlayer.Agent.Player.BenDaglish
+ Thomas Neumann
+ Polycode
+ NostalgicPlayer
+ Plays modules created by Ben Daglish
+ 2.1.1
+
+
+
+ bin\BenDaglish.xml
+
+
+
+ bin\BenDaglish.xml
+ true
+ portable
+ true
+
+
+
+
+
+ false
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
diff --git a/Source/Agents/Players/BenDaglish/BenDaglishWorker.cs b/Source/Agents/Players/BenDaglish/BenDaglishWorker.cs
new file mode 100644
index 00000000..db0e42ba
--- /dev/null
+++ b/Source/Agents/Players/BenDaglish/BenDaglishWorker.cs
@@ -0,0 +1,2245 @@
+/******************************************************************************/
+/* This source, or parts thereof, may be used in any software as long the */
+/* license of NostalgicPlayer is keep. See the LICENSE file for more */
+/* information. */
+/******************************************************************************/
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Polycode.NostalgicPlayer.Agent.Player.BenDaglish.Containers;
+using Polycode.NostalgicPlayer.Kit.Bases;
+using Polycode.NostalgicPlayer.Kit.Containers;
+using Polycode.NostalgicPlayer.Kit.Interfaces;
+using Polycode.NostalgicPlayer.Kit.Streams;
+
+namespace Polycode.NostalgicPlayer.Agent.Player.BenDaglish
+{
+ ///
+ /// Main worker class
+ ///
+ internal class BenDaglishWorker : ModulePlayerWithSubSongDurationAgentBase
+ {
+ private int subSongListOffset;
+ private int trackOffsetTableOffset;
+ private int tracksOffset;
+ private int sampleInfoOffsetTableOffset;
+
+ private List subSongs;
+ private Dictionary positionLists;
+ private byte[][] tracks;
+ private Sample[] samples;
+
+ private Features features;
+
+ private GlobalPlayingInfo playingInfo;
+ private VoiceInfo[] voices;
+ private VoicePlaybackInfo[] voicePlaybackInfo;
+
+ private const int InfoPositionLine = 3;
+ private const int InfoTrackLine = 4;
+
+ #region IPlayerAgent implementation
+ /********************************************************************/
+ ///
+ /// Returns the file extensions that identify this player
+ ///
+ /********************************************************************/
+ public override string[] FileExtensions => [ "bd" ];
+
+
+
+ /********************************************************************/
+ ///
+ /// Test the file to see if it could be identified
+ ///
+ /********************************************************************/
+ public override AgentResult Identify(PlayerFileInfo fileInfo)
+ {
+ ModuleStream moduleStream = fileInfo.ModuleStream;
+
+ // Check the module size
+ if (moduleStream.Length < 0x1600)
+ return AgentResult.Unknown;
+
+ // Read the first part of the file, so it is easier to search
+ byte[] buffer = new byte[0x3000];
+
+ moduleStream.Seek(0, SeekOrigin.Begin);
+ moduleStream.ReadInto(buffer, 0, buffer.Length);
+
+ return TestModule(buffer);
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Returns the description and value on the line given. If the line
+ /// is out of range, false is returned
+ ///
+ /********************************************************************/
+ public override bool GetInformationString(int line, out string description, out string value)
+ {
+ // Find out which line to take
+ switch (line)
+ {
+ // Number of positions
+ case 0:
+ {
+ description = Resources.IDS_BD_INFODESCLINE0;
+ value = FormatPositionLengths();
+ break;
+ }
+
+ // Used tracks
+ case 1:
+ {
+ description = Resources.IDS_BD_INFODESCLINE1;
+ value = tracks.Length.ToString();
+ break;
+ }
+
+ // Used samples
+ case 2:
+ {
+ description = Resources.IDS_BD_INFODESCLINE2;
+ value = samples.Length.ToString();
+ break;
+ }
+
+ // Playing positions
+ case 3:
+ {
+ description = Resources.IDS_BD_INFODESCLINE3;
+ value = FormatPositions();
+ break;
+ }
+
+ // Playing tracks
+ case 4:
+ {
+ description = Resources.IDS_BD_INFODESCLINE4;
+ value = FormatTracks();
+ break;
+ }
+
+ default:
+ {
+ description = null;
+ value = null;
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+ #endregion
+
+ #region IModulePlayerAgent implementation
+ /********************************************************************/
+ ///
+ /// Will load the file into memory
+ ///
+ /********************************************************************/
+ public override AgentResult Load(PlayerFileInfo fileInfo, out string errorMessage)
+ {
+ errorMessage = string.Empty;
+
+ try
+ {
+ ModuleStream moduleStream = fileInfo.ModuleStream;
+
+ if (!LoadSubSongInfo(moduleStream))
+ {
+ errorMessage = Resources.IDS_BD_ERR_LOADING_SUBSONG;
+ return AgentResult.Error;
+ }
+
+ if (!LoadPositionLists(moduleStream))
+ {
+ errorMessage = Resources.IDS_BD_ERR_LOADING_POSITION_LISTS;
+ return AgentResult.Error;
+ }
+
+ if (!LoadTracks(moduleStream))
+ {
+ errorMessage = Resources.IDS_BD_ERR_LOADING_TRACKS;
+ return AgentResult.Error;
+ }
+
+ if (!LoadSampleInfo(moduleStream, out uint[] sampleDataOffsets))
+ {
+ errorMessage = Resources.IDS_BD_ERR_LOADING_SAMPLEINFO;
+ return AgentResult.Error;
+ }
+
+ if (!LoadSampleData(moduleStream, sampleDataOffsets))
+ {
+ errorMessage = Resources.IDS_BD_ERR_LOADING_SAMPLES;
+ return AgentResult.Error;
+ }
+ }
+ catch (Exception)
+ {
+ Cleanup();
+ throw;
+ }
+
+ // Everything is loaded alright
+ return AgentResult.Ok;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Cleanup the player
+ ///
+ /********************************************************************/
+ public override void CleanupPlayer()
+ {
+ Cleanup();
+
+ base.CleanupPlayer();
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Initializes the current song
+ ///
+ /********************************************************************/
+ public override bool InitSound(int songNumber, out string errorMessage)
+ {
+ if (!base.InitSound(songNumber, out errorMessage))
+ return false;
+
+ InitializeSound(songNumber);
+
+ return true;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// This is the main player method
+ ///
+ /********************************************************************/
+ public override void Play()
+ {
+ HandleEffects();
+
+ if (features.EnableCounter)
+ {
+ playingInfo.Counter--;
+
+ if (playingInfo.Counter == 0)
+ {
+ playingInfo.Counter = 6;
+ return;
+ }
+ }
+
+ if (!playingInfo.EnablePlaying)
+ {
+ OnEndReached();
+
+ playingInfo.EnablePlaying = true;
+ RestartSong();
+
+ for (int i = 0; i < 4; i++)
+ VirtualChannels[i].Mute();
+
+ return;
+ }
+
+ playingInfo.EnablePlaying = voices[0].ChannelEnabled || voices[1].ChannelEnabled || voices[2].ChannelEnabled || voices[3].ChannelEnabled;
+
+ if (playingInfo.EnablePlaying)
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ VoiceInfo voiceInfo = voices[i];
+ VoicePlaybackInfo playbackInfo = voicePlaybackInfo[i];
+ IChannel channel = VirtualChannels[i];
+
+ HandleVoice(voiceInfo, playbackInfo, channel);
+ }
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Return information about sub-songs
+ ///
+ /********************************************************************/
+ public override SubSongInfo SubSongs => new SubSongInfo(subSongs.Count, 0);
+
+
+
+ /********************************************************************/
+ ///
+ /// Returns all the samples available in the module. If none, null
+ /// is returned
+ ///
+ /********************************************************************/
+ public override IEnumerable Samples
+ {
+ get
+ {
+ foreach (Sample sample in samples)
+ {
+ // Build frequency table
+ uint[] frequencies = new uint[10 * 12];
+
+ for (int j = 0; j < 8 * 12; j++)
+ {
+ uint period = (ushort)(((((Tables.FineTune[j] & 0xffff) * sample.FineTunePeriod) >> 16) + ((Tables.FineTune[j] >> 16) * sample.FineTunePeriod)) & 0xffff);
+ frequencies[8 * 12 - j] = 3546895U / period;
+ }
+
+ SampleInfo sampleInfo = new SampleInfo
+ {
+ Name = string.Empty,
+ Flags = SampleInfo.SampleFlag.None,
+ Type = SampleInfo.SampleType.Sample,
+ Volume = (ushort)(sample.Volume * 4),
+ Panning = -1,
+ Sample = sample.SampleData,
+ Length = sample.Length * 2U,
+ NoteFrequencies = frequencies
+ };
+
+ if (sample.LoopLength != 0)
+ {
+ sampleInfo.LoopStart = sample.LoopOffset;
+ sampleInfo.LoopLength = sample.LoopLength * 2U;
+ sampleInfo.Length += sampleInfo.LoopLength;
+ sampleInfo.Flags |= SampleInfo.SampleFlag.Loop;
+ }
+
+ yield return sampleInfo;
+ }
+ }
+ }
+ #endregion
+
+ #region ModulePlayerWithSubSongDurationAgentBase implementation
+ /********************************************************************/
+ ///
+ /// Initialize all internal structures when beginning duration
+ /// calculation on a new sub-song
+ ///
+ /********************************************************************/
+ protected override void InitDuration(int subSong)
+ {
+ InitializeSound(subSong);
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Create a snapshot of all the internal structures and return it
+ ///
+ /********************************************************************/
+ protected override ISnapshot CreateSnapshot()
+ {
+ return new Snapshot(playingInfo, voices, voicePlaybackInfo);
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Initialize internal structures based on the snapshot given
+ ///
+ /********************************************************************/
+ protected override bool SetSnapshot(ISnapshot snapshot, out string errorMessage)
+ {
+ errorMessage = string.Empty;
+
+ // Start to make a clone of the snapshot
+ Snapshot currentSnapshot = (Snapshot)snapshot;
+ Snapshot clonedSnapshot = new Snapshot(currentSnapshot.PlayingInfo, currentSnapshot.Voices, currentSnapshot.PlaybackVoices);
+
+ playingInfo = clonedSnapshot.PlayingInfo;
+ voices = clonedSnapshot.Voices;
+ voicePlaybackInfo = clonedSnapshot.PlaybackVoices;
+
+ UpdateModuleInformation();
+
+ return true;
+ }
+ #endregion
+
+ #region Private methods
+ /********************************************************************/
+ ///
+ /// Test the file to see if it's a Ben Daglish player and extract
+ /// needed information
+ ///
+ /********************************************************************/
+ private AgentResult TestModule(byte[] buffer)
+ {
+ // First check some places in the file for required
+ // assembler code
+ if ((buffer[0] != 0x60) || (buffer[1] != 0x00) || (buffer[4] != 0x60) || (buffer[5] != 0x00) || (buffer[10] != 0x60) || (buffer[11] != 00))
+ return AgentResult.Unknown;
+
+ // Find the init function
+ int startOfInit = (((sbyte)buffer[2] << 8) | buffer[3]) + 2;
+ if (startOfInit >= (buffer.Length - 14))
+ return AgentResult.Unknown;
+
+ if ((buffer[startOfInit] != 0x3f) || (buffer[startOfInit + 1] != 0x00) || (buffer[startOfInit + 2] != 0x61) || (buffer[startOfInit + 3] != 0x00) ||
+ (buffer[startOfInit + 6] != 0x3d) || (buffer[startOfInit + 7] != 0x7c) ||
+ (buffer[startOfInit + 12] != 0x41) || (buffer[startOfInit + 13] != 0xfa))
+ {
+ return AgentResult.Unknown;
+ }
+
+ // Find the play function
+ int startOfPlay = (((sbyte)buffer[6] << 8) | buffer[7]) + 4 + 2;
+ if (startOfPlay >= buffer.Length)
+ return AgentResult.Unknown;
+
+ if (!ExtractInfoFromInitFunction(buffer, startOfInit))
+ return AgentResult.Unknown;
+
+ if (!ExtractInfoFromPlayFunction(buffer, startOfPlay))
+ return AgentResult.Unknown;
+
+ if (!FindFeatures(buffer, startOfPlay))
+ return AgentResult.Unknown;
+
+ return AgentResult.Ok;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Try to find the init function in the player and extract needed
+ /// information from it
+ ///
+ /********************************************************************/
+ private bool ExtractInfoFromInitFunction(byte[] searchBuffer, int startOfInit)
+ {
+ int searchLength = searchBuffer.Length;
+ int index;
+
+ // Find sub-song information offset
+ for (index = startOfInit; index < (searchLength - 6); index += 2)
+ {
+ if ((searchBuffer[index] == 0x41) && (searchBuffer[index + 1] == 0xfa) && (searchBuffer[index + 4] == 0x22) && (searchBuffer[index + 5] == 0x08))
+ break;
+ }
+
+ if (index >= (searchLength - 6))
+ return false;
+
+ subSongListOffset = (((sbyte)searchBuffer[index + 2] << 8) | searchBuffer[index + 3]) + index + 2;
+ index += 4;
+
+ // Find sample information offset table offset
+ for (; index < (searchLength - 6); index += 2)
+ {
+ if ((searchBuffer[index] == 0x41) && (searchBuffer[index + 1] == 0xfa) && (searchBuffer[index + 4] == 0x23) && (searchBuffer[index + 5] == 0x48))
+ break;
+ }
+
+ if (index >= (searchLength - 6))
+ return false;
+
+ sampleInfoOffsetTableOffset = (((sbyte)searchBuffer[index + 2] << 8) | searchBuffer[index + 3]) + index + 2;
+
+ return true;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Try to find the play function in the player and extract needed
+ /// information from it
+ ///
+ /********************************************************************/
+ private bool ExtractInfoFromPlayFunction(byte[] searchBuffer, int startOfPlay)
+ {
+ int searchLength = searchBuffer.Length;
+ int index;
+
+ // Find track offset table offset
+ for (index = startOfPlay; index < (searchLength - 6); index += 2)
+ {
+ if ((searchBuffer[index] == 0x47) && (searchBuffer[index + 1] == 0xfa) && (((searchBuffer[index + 4] == 0x48) && (searchBuffer[index + 5] == 0x80)) || ((searchBuffer[index + 4] == 0xd0) && (searchBuffer[index + 5] == 0x40))))
+ break;
+ }
+
+ if (index >= (searchLength - 6))
+ return false;
+
+ trackOffsetTableOffset = (((sbyte)searchBuffer[index + 2] << 8) | searchBuffer[index + 3]) + index + 2;
+ index += 4;
+
+ // Find tracks offset
+ for (; index < (searchLength - 6); index += 2)
+ {
+ if ((searchBuffer[index] == 0x47) && (searchBuffer[index + 1] == 0xfa) && (searchBuffer[index + 4] == 0xd6) && (searchBuffer[index + 5] == 0xc0))
+ break;
+ }
+
+ if (index >= (searchLength - 6))
+ return false;
+
+ tracksOffset = (((sbyte)searchBuffer[index + 2] << 8) | searchBuffer[index + 3]) + index + 2;
+
+ return true;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Check the player code to see which features are enabled or
+ /// disabled
+ ///
+ /********************************************************************/
+ private bool FindFeatures(byte[] searchBuffer, int startOfPlay)
+ {
+ features = new Features();
+
+ if (!FindFeaturesInPlayMethod(searchBuffer, startOfPlay))
+ return false;
+
+ if (!FindFeaturesInHandleEffectsMethod(searchBuffer, startOfPlay))
+ return false;
+
+ if (!FindFeaturesInParseTrackMethod(searchBuffer, startOfPlay))
+ return false;
+
+ return true;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ ///
+ ///
+ /********************************************************************/
+ private bool FindFeaturesInPlayMethod(byte[] searchBuffer, int startOfPlay)
+ {
+ int searchLength = searchBuffer.Length;
+ int index;
+
+ // Check for counter feature
+ features.EnableCounter = false;
+
+ if (startOfPlay >= (searchLength - 16))
+ return false;
+
+ if ((searchBuffer[startOfPlay + 4] == 0x10) && (searchBuffer[startOfPlay + 5] == 0x3a) && (searchBuffer[startOfPlay + 8] == 0x67) && (searchBuffer[startOfPlay + 14] == 0x53) && (searchBuffer[startOfPlay + 15] == 0x50))
+ {
+ index = (((sbyte)searchBuffer[startOfPlay + 6] << 8) | searchBuffer[startOfPlay + 7]) + startOfPlay + 6;
+ if (index >= searchLength)
+ return false;
+
+ features.EnableCounter = searchBuffer[index] != 0;
+ }
+
+ // Check effect calls
+ features.EnablePortamento = false;
+ features.EnableVolumeFade = false;
+
+ for (index = startOfPlay; index < (searchLength - 2); index += 2)
+ {
+ if ((searchBuffer[index] == 0x53) && (searchBuffer[index + 1] == 0x2c))
+ break;
+ }
+
+ if (index >= (searchLength - 2))
+ return false;
+
+ for (; index >= startOfPlay; index -= 2)
+ {
+ if ((searchBuffer[index] == 0x49) && (searchBuffer[index + 1] == 0xfa))
+ break;
+
+ if ((searchBuffer[index] == 0x61) && (searchBuffer[index + 1] == 0x00))
+ {
+ int methodIndex = (((sbyte)searchBuffer[index + 2] << 8) | searchBuffer[index + 3]) + index + 2;
+
+ if (methodIndex >= (searchLength - 14))
+ return false;
+
+ if ((searchBuffer[methodIndex] == 0x4a) && (searchBuffer[methodIndex + 1] == 0x2c) && (searchBuffer[methodIndex + 4] == 0x67) &&
+ (searchBuffer[methodIndex + 6] == 0x6a) && (searchBuffer[methodIndex + 8] == 0x30) && (searchBuffer[methodIndex + 9] == 0x29))
+ {
+ features.EnablePortamento = true;
+ }
+ else if ((searchBuffer[methodIndex] == 0x4a) && (searchBuffer[methodIndex + 1] == 0x2c) && (searchBuffer[methodIndex + 4] == 0x67) &&
+ (searchBuffer[methodIndex + 6] == 0x4a) && (searchBuffer[methodIndex + 7] == 0x2c) && (searchBuffer[methodIndex + 10] == 0x67))
+ {
+ features.EnableVolumeFade = true;
+ }
+ else
+ return false;
+ }
+ }
+
+ // Check for position effects
+ features.MaxTrackValue = 0x80;
+
+ for (index = startOfPlay; index < (searchLength - 6); index += 2)
+ {
+ if ((searchBuffer[index] == 0x10) && (searchBuffer[index + 1] == 0x1b))
+ break;
+ }
+
+ if (index >= (searchLength - 6))
+ return false;
+
+ if (((searchBuffer[index + 2] == 0xb0) && (searchBuffer[index + 3] == 0x3c)) || ((searchBuffer[index + 2] == 0x0c) && (searchBuffer[index + 3] == 0x00)))
+ features.MaxTrackValue = searchBuffer[index + 5];
+
+ for (index += 4; index < (searchLength - 6); index += 2)
+ {
+ if ((((searchBuffer[index] == 0xb0) && (searchBuffer[index + 1] == 0x3c)) || ((searchBuffer[index] == 0x0c) && (searchBuffer[index + 1] == 0x00))) && (searchBuffer[index + 4] == 0x6c))
+ break;
+ }
+
+ if (index >= (searchLength - 6))
+ return false;
+
+ int effect = (searchBuffer[index + 2] << 8) | searchBuffer[index + 3];
+ features.EnableC0TrackLoop = effect == 0x00c0;
+ features.EnableF0TrackLoop = effect == 0x00f0;
+
+ index = searchBuffer[index + 5] + index + 6;
+
+ if ((searchBuffer[index] == 0x02) && (searchBuffer[index + 1] == 0x40))
+ features.SetSampleMappingVersion = 1;
+ else if ((searchBuffer[index] == 0x04) && (searchBuffer[index + 1] == 0x00))
+ features.SetSampleMappingVersion = 2;
+ else
+ return false;
+
+ return true;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ ///
+ ///
+ /********************************************************************/
+ private bool FindFeaturesInHandleEffectsMethod(byte[] searchBuffer, int startOfPlay)
+ {
+ int searchLength = searchBuffer.Length;
+ int index;
+
+ if ((searchBuffer[startOfPlay] != 0x61) || (searchBuffer[startOfPlay + 1] != 0x00))
+ return false;
+
+ int startOfHandleEffects = (((sbyte)searchBuffer[startOfPlay + 2] << 8) | searchBuffer[startOfPlay + 3]) + startOfPlay + 2;
+
+ // Find call to sample handler callBack method
+ for (index = startOfHandleEffects; index < (searchLength - 2); index += 2)
+ {
+ if ((searchBuffer[index] == 0x4e) && (searchBuffer[index + 1] == 0x90))
+ break;
+ }
+
+ if (index >= (searchLength - 2))
+ return false;
+
+ int callBackIndex = index;
+
+ // Search back after effect method calls
+ features.EnableSampleEffects = false;
+ features.EnableFinalVolumeSlide = false;
+
+ for (; index >= startOfHandleEffects; index -= 2)
+ {
+ if ((searchBuffer[index] == 0x4e) && (searchBuffer[index + 1] == 0x75))
+ break;
+
+ if ((searchBuffer[index] == 0x61) && (searchBuffer[index + 1] == 0x00))
+ {
+ int methodIndex = (((sbyte)searchBuffer[index + 2] << 8) | searchBuffer[index + 3]) + index + 2;
+
+ if (methodIndex >= (searchLength - 14))
+ return false;
+
+ if ((searchBuffer[methodIndex] == 0x30) && (searchBuffer[methodIndex + 1] == 0x2b) && (searchBuffer[methodIndex + 4] == 0x67) &&
+ (((searchBuffer[methodIndex + 6] == 0xb0) && (searchBuffer[methodIndex + 7] == 0x7c)) || ((searchBuffer[methodIndex + 6] == 0x0c) && (searchBuffer[methodIndex + 7] == 0x40))) &&
+ (searchBuffer[methodIndex + 8] == 0xff) && (searchBuffer[methodIndex + 9] == 0xff))
+ {
+ features.EnableSampleEffects = true;
+ }
+ else if ((searchBuffer[methodIndex] == 0x30) && (searchBuffer[methodIndex + 1] == 0x2b) && (searchBuffer[methodIndex + 4] == 0x67) &&
+ (searchBuffer[methodIndex + 6] == 0x53) && (searchBuffer[methodIndex + 7] == 0x6b))
+ {
+ features.EnableFinalVolumeSlide = true;
+ }
+ else
+ return false;
+ }
+ }
+
+ if ((searchBuffer[callBackIndex + 6] != 0x6e) && (searchBuffer[callBackIndex + 6] != 0x66))
+ return false;
+
+ index = searchBuffer[callBackIndex + 7] + callBackIndex + 8;
+ if (index >= (searchLength - 6))
+ return false;
+
+ // Check for setting DMA in sample handlers
+ features.SetDmaInSampleHandlers = true;
+
+ for (; index < searchLength; index++)
+ {
+ if ((searchBuffer[index] == 0x4e) && (searchBuffer[index + 1] == 0x75))
+ break;
+ }
+
+ if (index >= searchLength)
+ return false;
+
+ if ((searchBuffer[index - 2] == 0x00) && (searchBuffer[index - 1] == 0x96))
+ features.SetDmaInSampleHandlers = false;
+
+ // Check for master volume fade feature
+ if ((searchBuffer[startOfHandleEffects] == 0x61) && (searchBuffer[startOfHandleEffects + 1] == 0x00))
+ {
+ index = (((sbyte)searchBuffer[startOfHandleEffects + 2] << 8) | searchBuffer[startOfHandleEffects + 3]) + startOfHandleEffects + 2;
+
+ if (index >= (searchLength - 24))
+ return false;
+
+ features.MasterVolumeFadeVersion = -1;
+
+ if ((searchBuffer[index] == 0x30) && (searchBuffer[index + 1] == 0x3a) && (searchBuffer[index + 4] == 0x67) && (searchBuffer[index + 5] == 00) &&
+ (searchBuffer[index + 8] == 0x41) && (searchBuffer[index + 9] == 0xfa) && (searchBuffer[index + 18] == 0x30) && (searchBuffer[index + 19] == 0x80))
+ {
+ features.MasterVolumeFadeVersion = 1;
+ }
+ else if ((searchBuffer[index] == 0x30) && (searchBuffer[index + 1] == 0x39) && (searchBuffer[index + 6] == 0x67) && (searchBuffer[index + 7] == 00) &&
+ (searchBuffer[index + 10] == 0x41) && (searchBuffer[index + 11] == 0xf9) && (searchBuffer[index + 22] == 0x30) && (searchBuffer[index + 23] == 0x80))
+ {
+ features.MasterVolumeFadeVersion = 1;
+ }
+ else if ((searchBuffer[index] == 0x10) && (searchBuffer[index + 1] == 0x3a) && (searchBuffer[index + 4] == 0x67) && (searchBuffer[index + 5] == 00) &&
+ (searchBuffer[index + 8] == 0x41) && (searchBuffer[index + 9] == 0xfa) && (searchBuffer[index + 18] == 0x53) && (searchBuffer[index + 19] == 0x00))
+ {
+ features.MasterVolumeFadeVersion = 2;
+ }
+ else
+ return false;
+ }
+
+ return true;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ ///
+ ///
+ /********************************************************************/
+ private bool FindFeaturesInParseTrackMethod(byte[] searchBuffer, int startOfPlay)
+ {
+ int searchLength = searchBuffer.Length;
+ int index;
+
+ for (index = startOfPlay; index < (searchLength - 4); index += 2)
+ {
+ if ((searchBuffer[index] == 0x60) && (searchBuffer[index + 1] == 0x00))
+ break;
+ }
+
+ if (index >= (searchLength - 4))
+ return false;
+
+ index = (((sbyte)searchBuffer[index + 2] << 8) | searchBuffer[index + 3]) + index + 2;
+
+ if (index >= searchLength)
+ return false;
+
+ int startOfParseTrack = index;
+
+ for (; index < (searchLength - 8); index += 2)
+ {
+ if ((searchBuffer[index] == 0x4a) && (searchBuffer[index + 1] == 0x2c) && (searchBuffer[index + 4] == 0x67))
+ break;
+ }
+
+ if (index >= (searchLength - 8))
+ return false;
+
+ features.CheckForTicks = (searchBuffer[index + 6] == 0x4a) && (searchBuffer[index + 7] == 0x2c);
+
+ for (index += 8; index < (searchLength - 6); index += 2)
+ {
+ if ((searchBuffer[index] == 0x72) && (searchBuffer[index + 1] == 0x00) && (searchBuffer[index + 2] == 0x12) && (searchBuffer[index + 3] == 0x1b))
+ break;
+ }
+
+ if (index >= (searchLength - 6))
+ return false;
+
+ features.ExtraTickArg = searchBuffer[index + 4] == 0x66;
+
+ for (index = startOfParseTrack; index < (searchLength - 4); index += 2)
+ {
+ if ((searchBuffer[index] == 0x61) && (searchBuffer[index + 1] == 0x00))
+ break;
+ }
+
+ if (index >= (searchLength - 4))
+ return false;
+
+ index = (((sbyte)searchBuffer[index + 2] << 8) | searchBuffer[index + 3]) + index + 2;
+
+ if (index >= searchLength)
+ return false;
+
+ return FindFeaturesInParseTrackEffectMethod(searchBuffer, index);
+ }
+
+
+
+ /********************************************************************/
+ ///
+ ///
+ ///
+ /********************************************************************/
+ private bool FindFeaturesInParseTrackEffectMethod(byte[] searchBuffer, int startOfMethod)
+ {
+ int searchLength = searchBuffer.Length;
+ int index;
+
+ if (((searchBuffer[startOfMethod + 2] != 0xb0) || (searchBuffer[startOfMethod + 3] != 0x3c)) && ((searchBuffer[startOfMethod + 2] != 0x0c) || (searchBuffer[startOfMethod + 3] != 0x00)))
+ return false;
+
+ features.MaxSampleMappingValue = searchBuffer[startOfMethod + 5];
+ index = startOfMethod + 8;
+
+ if (index >= (searchLength - 4))
+ return false;
+
+ if ((searchBuffer[index] != 0x02) || (searchBuffer[index + 1] != 0x40) || (searchBuffer[index + 2] != 0x00))
+ return false;
+
+ if (searchBuffer[index + 3] == 0x07)
+ features.GetSampleMappingVersion = 1;
+ else if (searchBuffer[index + 3] == 0xff)
+ features.GetSampleMappingVersion = 2;
+ else
+ return true;
+
+ for (index += 4; index < (searchLength - 6); index += 2)
+ {
+ if ((((searchBuffer[index] == 0xb0) && (searchBuffer[index + 1] == 0x3c)) || ((searchBuffer[index] == 0x0c) && (searchBuffer[index + 1] == 0x00))) && (searchBuffer[index + 4] == 0x6c))
+ break;
+ }
+
+ if (index >= (searchLength - 6))
+ return false;
+
+ features.Uses9xTrackEffects = (searchBuffer[index + 3] & 0xf0) == 0x90;
+ features.UsesCxTrackEffects = (searchBuffer[index + 3] & 0xf0) == 0xc0;
+
+ return true;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Load the sub-song information for all sub-songs
+ ///
+ /********************************************************************/
+ private bool LoadSubSongInfo(ModuleStream moduleStream)
+ {
+ subSongs = new List();
+
+ // Seek to the sub-song list
+ moduleStream.Seek(subSongListOffset, SeekOrigin.Begin);
+ int firstPositionList = int.MaxValue;
+
+ do
+ {
+ SongInfo song = new SongInfo();
+
+ moduleStream.ReadArray_B_UINT16s(song.PositionLists, 0, 4);
+
+ if (moduleStream.EndOfStream)
+ return false;
+
+ firstPositionList = Math.Min(firstPositionList, song.PositionLists.Min());
+
+ subSongs.Add(song);
+ }
+ while (moduleStream.Position < (subSongListOffset + firstPositionList));
+
+ return true;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Load all the position lists
+ ///
+ /********************************************************************/
+ private bool LoadPositionLists(ModuleStream moduleStream)
+ {
+ positionLists = new Dictionary();
+
+ foreach (SongInfo song in subSongs)
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ ushort positionListOffset = song.PositionLists[i];
+ if (positionLists.ContainsKey(positionListOffset))
+ continue;
+
+ // Seek to position list
+ moduleStream.Seek(subSongListOffset + positionListOffset, SeekOrigin.Begin);
+
+ // Load the position list
+ byte[] positionList = LoadSinglePositionList(moduleStream);
+ if (positionList == null)
+ return false;
+
+ positionLists[positionListOffset] = positionList;
+ }
+ }
+
+ return true;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Load a single position list
+ ///
+ /********************************************************************/
+ private byte[] LoadSinglePositionList(ModuleStream moduleStream)
+ {
+ List positionListBytes = new List();
+
+ for (;;)
+ {
+ byte cmd = moduleStream.Read_UINT8();
+
+ if (moduleStream.EndOfStream)
+ return null;
+
+ positionListBytes.Add(cmd);
+
+ if (cmd == 0xff) // End of position list?
+ break;
+
+ int argCount = FindPositionCommandArgumentCount(cmd);
+ if (argCount == -1)
+ return null;
+
+ for (; argCount > 0; argCount--)
+ positionListBytes.Add(moduleStream.Read_UINT8());
+ }
+
+ return positionListBytes.ToArray();
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Find number of arguments for the given position command
+ ///
+ /********************************************************************/
+ private int FindPositionCommandArgumentCount(byte cmd)
+ {
+ if (cmd < features.MaxTrackValue)
+ return 0;
+
+ if (features.EnableC0TrackLoop)
+ {
+ if (cmd < 0xa0)
+ return 0;
+
+ if (cmd < 0xc8)
+ return 1;
+ }
+
+ if (features.EnableF0TrackLoop)
+ {
+ if (cmd < 0xf0)
+ return 0;
+
+ if (cmd < 0xf8)
+ return 1;
+ }
+
+ if ((cmd == 0xfd) && (features.MasterVolumeFadeVersion > 0))
+ return 1;
+
+ if (cmd == 0xfe)
+ return 1;
+
+ return -1;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Load all the tracks
+ ///
+ /********************************************************************/
+ private bool LoadTracks(ModuleStream moduleStream)
+ {
+ // Find number of tracks
+ int numberOfTracks = (subSongListOffset - trackOffsetTableOffset) / 2;
+
+ tracks = new byte[numberOfTracks][];
+
+ // Read the track offsets
+ ushort[] trackOffsetTable = new ushort[numberOfTracks];
+
+ moduleStream.Seek(trackOffsetTableOffset, SeekOrigin.Begin);
+ moduleStream.ReadArray_B_UINT16s(trackOffsetTable, 0, numberOfTracks);
+
+ if (moduleStream.EndOfStream)
+ return false;
+
+ for (int i = 0; i < numberOfTracks; i++)
+ {
+ moduleStream.Seek(tracksOffset + trackOffsetTable[i], SeekOrigin.Begin);
+
+ byte[] track = LoadSingleTrack(moduleStream);
+ if (track == null)
+ return false;
+
+ tracks[i] = track;
+ }
+
+ return true;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Load a single track
+ ///
+ /********************************************************************/
+ private byte[] LoadSingleTrack(ModuleStream moduleStream)
+ {
+ List trackBytes = new List();
+
+ for (;;)
+ {
+ byte cmd = moduleStream.Read_UINT8();
+
+ if (moduleStream.EndOfStream)
+ return null;
+
+ trackBytes.Add(cmd);
+
+ if (cmd == 0xff) // End of track?
+ break;
+
+ byte nextByte = moduleStream.Read_UINT8();
+
+ int argCount = FindTrackCommandArgumentCount(cmd, nextByte);
+ if (argCount == -1)
+ return null;
+
+ if (argCount > 0)
+ {
+ trackBytes.Add(nextByte);
+
+ for (argCount--; argCount > 0; argCount--)
+ trackBytes.Add(moduleStream.Read_UINT8());
+ }
+ else
+ moduleStream.Seek(-1, SeekOrigin.Current);
+ }
+
+ return trackBytes.ToArray();
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Find number of arguments for the given track command
+ ///
+ /********************************************************************/
+ private int FindTrackCommandArgumentCount(byte cmd, byte nextByte)
+ {
+ if (cmd < 0x7f)
+ {
+ if (features.ExtraTickArg && (nextByte == 0))
+ return 2;
+
+ return 1;
+ }
+
+ if (cmd == 0x7f)
+ return 1;
+
+ if (cmd <= features.MaxSampleMappingValue)
+ return 0;
+
+ if ((features.UsesCxTrackEffects && (cmd < 0xc0)) || (features.Uses9xTrackEffects && (cmd < 0x9b)))
+ return 0;
+
+ switch (cmd)
+ {
+ case 0xc0 when features.UsesCxTrackEffects && features.EnablePortamento:
+ case 0x9b when features.Uses9xTrackEffects && features.EnablePortamento:
+ return 3;
+
+ case 0xc1 when features.UsesCxTrackEffects && features.EnablePortamento:
+ case 0x9c when features.Uses9xTrackEffects && features.EnablePortamento:
+ return 0;
+
+ case 0xc2 when features.UsesCxTrackEffects && features.EnableVolumeFade:
+ case 0x9d when features.Uses9xTrackEffects && features.EnableVolumeFade:
+ return 3;
+
+ case 0xc3 when features.UsesCxTrackEffects && features.EnableVolumeFade:
+ case 0x9e when features.Uses9xTrackEffects && features.EnableVolumeFade:
+ return 0;
+
+ case 0xc4 when features.UsesCxTrackEffects && features.EnablePortamento:
+ case 0x9f when features.Uses9xTrackEffects && features.EnablePortamento:
+ return 1;
+
+ case 0xc5 when features.UsesCxTrackEffects && features.EnablePortamento:
+ case 0xa0 when features.Uses9xTrackEffects && features.EnablePortamento:
+ return 0;
+
+ case 0xc6 when features.UsesCxTrackEffects && features.EnableVolumeFade:
+ case 0xa1 when features.Uses9xTrackEffects && features.EnableVolumeFade:
+ return features.EnableFinalVolumeSlide ? 3 : 1;
+
+ case 0xc7 when features.UsesCxTrackEffects && features.EnableFinalVolumeSlide:
+ case 0xa2 when features.Uses9xTrackEffects && features.EnableFinalVolumeSlide:
+ return 0;
+ }
+
+ return -1;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Load all the sample information
+ ///
+ /********************************************************************/
+ private bool LoadSampleInfo(ModuleStream moduleStream, out uint[] sampleDataOffsets)
+ {
+ // First read the sample information offset table
+ List offsetTable = new List();
+ uint firstSampleInfo = uint.MaxValue;
+
+ moduleStream.Seek(sampleInfoOffsetTableOffset, SeekOrigin.Begin);
+
+ do
+ {
+ uint offset = moduleStream.Read_B_UINT32();
+
+ if (moduleStream.EndOfStream)
+ {
+ sampleDataOffsets = null;
+ return false;
+ }
+
+ firstSampleInfo = Math.Min(firstSampleInfo, offset);
+ offsetTable.Add(offset);
+ }
+ while (moduleStream.Position < (sampleInfoOffsetTableOffset + firstSampleInfo));
+
+ // Now read the sample information
+ samples = new Sample[offsetTable.Count];
+ sampleDataOffsets = new uint[offsetTable.Count];
+
+ for (short i = 0; i < samples.Length; i++)
+ {
+ moduleStream.Seek(sampleInfoOffsetTableOffset + offsetTable[i], SeekOrigin.Begin);
+
+ Sample sample = new Sample();
+ sample.SampleNumber = i;
+
+ uint sampleDataOffset = moduleStream.Read_B_UINT32();
+
+ sample.LoopOffset = moduleStream.Read_B_UINT32();
+ if (sample.LoopOffset > 0)
+ sample.LoopOffset -= sampleDataOffset;
+
+ sample.Length = moduleStream.Read_B_UINT16();
+ sample.LoopLength = moduleStream.Read_B_UINT16();
+
+ sample.Volume = moduleStream.Read_B_UINT16();
+ sample.VolumeFadeSpeed = moduleStream.Read_B_INT16();
+
+ sample.PortamentoDuration = moduleStream.Read_B_INT16();
+ sample.PortamentoAddValue = moduleStream.Read_B_INT16();
+
+ sample.VibratoDepth = moduleStream.Read_B_UINT16();
+ sample.VibratoAddValue = moduleStream.Read_B_UINT16();
+
+ sample.NoteTranspose = moduleStream.Read_B_INT16();
+ sample.FineTunePeriod = moduleStream.Read_B_UINT16();
+
+ if (moduleStream.EndOfStream)
+ return false;
+
+ sampleDataOffsets[i] = sampleDataOffset;
+ samples[i] = sample;
+ }
+
+ return true;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Load all the samples
+ ///
+ /********************************************************************/
+ private bool LoadSampleData(ModuleStream moduleStream, uint[] sampleDataOffsets)
+ {
+ for (int i = 0; i < samples.Length; i++)
+ {
+ Sample sample = samples[i];
+
+ moduleStream.Seek(sampleInfoOffsetTableOffset + sampleDataOffsets[i], SeekOrigin.Begin);
+
+ int sampleEnd1 = sample.Length * 2;
+ int sampleEnd2 = (int)(sample.LoopOffset + (sample.LoopLength * 2));
+ int length = Math.Max(sampleEnd1, sampleEnd2);
+
+ sample.SampleData = moduleStream.ReadSampleData(i, length, out int readBytes);
+
+ if (readBytes != length)
+ return false;
+ }
+
+ return true;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Initialize sound structures
+ ///
+ /********************************************************************/
+ private void InitializeSound(int subSong)
+ {
+ SongInfo song = subSongs[subSong];
+
+ playingInfo = new GlobalPlayingInfo
+ {
+ EnablePlaying = true,
+
+ MasterVolume = 64,
+ MasterVolumeFadeSpeed = 0,
+ MasterVolumeFadeSpeedCounter = 1,
+
+ Counter = 6
+ };
+
+ voices = new VoiceInfo[4];
+ voicePlaybackInfo = new VoicePlaybackInfo[4];
+
+ for (int i = 0; i < 4; i++)
+ {
+ voices[i] = new VoiceInfo
+ {
+ ChannelEnabled = true,
+
+ PositionList = positionLists[song.PositionLists[i]],
+ CurrentPosition = 0,
+ NextPosition = 0,
+
+ PlayingTrack = 0,
+ Track = null,
+ NextTrackPosition = 0,
+
+ SwitchToNextPosition = true,
+ TrackLoopCounter = 1,
+ TicksLeftForNextTrackCommand = 1,
+
+ Transpose = 0,
+ TransposedNote = 0,
+ PreviousTransposedNote = 0,
+ UseNewNote = true,
+
+ Portamento1Enabled = 0,
+ Portamento2Enabled = false,
+ PortamentoStartDelay = 0,
+ PortamentoDuration = 0,
+ PortamentoDeltaNoteNumber = 0,
+
+ PortamentoControlFlag = 0,
+ PortamentoStartDelayCounter = 0,
+ PortamentoDurationCounter = 0,
+ PortamentoAddValue = 0,
+
+ VolumeFadeEnabled = false,
+ VolumeFadeInitSpeed = 0,
+ VolumeFadeDuration = 0,
+ VolumeFadeInitAddValue = 0,
+
+ VolumeFadeRunning = false,
+ VolumeFadeSpeed = 0,
+ VolumeFadeSpeedCounter = 0,
+ VolumeFadeDurationCounter = 0,
+ VolumeFadeAddValue = 0,
+ VolumeFadeValue = 0,
+
+ ChannelVolume = 0xffff,
+ ChannelVolumeSlideSpeed = 0,
+ ChannelVolumeSlideAddValue = 0,
+
+ SampleInfo = null,
+ SampleInfo2 = null
+ };
+
+ voicePlaybackInfo[i] = new VoicePlaybackInfo
+ {
+ PlayingSample = null,
+ SamplePlayTicksCounter = 0,
+
+ NotePeriod = 0,
+ FinalVolume = 0,
+
+ FinalVolumeSlideSpeed = 0,
+ FinalVolumeSlideSpeedCounter = 0,
+ FinalVolumeSlideAddValue = 0,
+
+ LoopDelayCounter = 0,
+
+ PortamentoAddValue = 0,
+
+ SamplePortamentoDuration = 0,
+ SamplePortamentoAddValue = 0,
+
+ SampleVibratoDepth = 0,
+ SampleVibratoAddValue = 0,
+
+ SamplePeriodAddValue = 0,
+
+ HandleSampleCallback = null,
+
+ DmaEnabled = false,
+ SampleNumber = -1,
+ SampleData = null,
+ SampleLength = 0
+ };
+ }
+
+ CreateSampleMapping();
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Will initialize the sample mapping table
+ ///
+ /********************************************************************/
+ private void CreateSampleMapping()
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ VoiceInfo voiceInfo = voices[i];
+
+ for (byte j = 0; j < voiceInfo.SampleMapping.Length; j++)
+ voiceInfo.SampleMapping[j] = j;
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Frees all the memory the player have allocated
+ ///
+ /********************************************************************/
+ private void Cleanup()
+ {
+ subSongs = null;
+ positionLists = null;
+ tracks = null;
+ samples = null;
+
+ features = null;
+
+ playingInfo = null;
+ voices = null;
+ voicePlaybackInfo = null;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ ///
+ ///
+ /********************************************************************/
+ private void HandleVoice(VoiceInfo voiceInfo, VoicePlaybackInfo playbackInfo, IChannel channel)
+ {
+ if (!voiceInfo.ChannelEnabled)
+ return;
+
+ if (features.EnablePortamento)
+ DoPortamento(voiceInfo, playbackInfo);
+
+ if (features.EnableVolumeFade)
+ DoVolumeFade(voiceInfo, playbackInfo, channel);
+
+ for (;;)
+ {
+ if (voiceInfo.SwitchToNextPosition)
+ {
+ voiceInfo.TrackLoopCounter--;
+
+ if (voiceInfo.TrackLoopCounter == 0)
+ {
+ // Go to next position
+ voiceInfo.TrackLoopCounter = 1;
+
+ if (TakeNextPosition(voiceInfo, playbackInfo))
+ return;
+ }
+ else
+ {
+ // Loop track
+ voiceInfo.NextTrackPosition = 0;
+ voiceInfo.SwitchToNextPosition = false;
+ }
+ }
+
+ if (!ParseTrack(voiceInfo, playbackInfo, channel))
+ break;
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Parse next commands in the position list and setup the track
+ ///
+ /********************************************************************/
+ private bool TakeNextPosition(VoiceInfo voiceInfo, VoicePlaybackInfo playbackInfo)
+ {
+ voiceInfo.CurrentPosition = voiceInfo.NextPosition;
+
+ int position = voiceInfo.NextPosition;
+ byte cmd;
+
+ for (;;)
+ {
+ cmd = voiceInfo.PositionList[position++];
+ if (cmd < features.MaxTrackValue)
+ break;
+
+ switch (cmd)
+ {
+ case 0xfe:
+ {
+ voiceInfo.Transpose = (sbyte)voiceInfo.PositionList[position++];
+ break;
+ }
+
+ case 0xff:
+ {
+ if ((playbackInfo.LoopDelayCounter == 0) || (playbackInfo.LoopDelayCounter == 0x8000))
+ voiceInfo.ChannelEnabled = false;
+
+ return true;
+ }
+
+ case 0xfd when (features.MasterVolumeFadeVersion > 0):
+ {
+ playingInfo.MasterVolumeFadeSpeed = (sbyte)voiceInfo.PositionList[position++];
+ break;
+ }
+
+ case < 0xf0 when features.EnableF0TrackLoop:
+ {
+ voiceInfo.TrackLoopCounter = (byte)(cmd - 0xc8);
+ break;
+ }
+
+ case < 0xc0 when features.EnableC0TrackLoop:
+ {
+ voiceInfo.TrackLoopCounter = (byte)(cmd & 0x1f);
+ break;
+ }
+
+ default:
+ {
+ int index = features.SetSampleMappingVersion == 1 ? cmd & 0x07 : cmd - 0xf0;
+ voiceInfo.SampleMapping[index] = (byte)(voiceInfo.PositionList[position++] / 4);
+ break;
+ }
+ }
+ }
+
+ voiceInfo.SwitchToNextPosition = false;
+ voiceInfo.NextPosition = position;
+
+ voiceInfo.PlayingTrack = cmd;
+ voiceInfo.Track = tracks[cmd];
+ voiceInfo.NextTrackPosition = 0;
+
+ ShowChannelPositions();
+ ShowTracks();
+
+ return false;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Parse next commands in the track
+ ///
+ /********************************************************************/
+ private bool ParseTrack(VoiceInfo voiceInfo, VoicePlaybackInfo playbackInfo, IChannel channel)
+ {
+ if (voiceInfo.Track == null)
+ {
+ // Position list have not set any track, so disable the channel
+ voiceInfo.ChannelEnabled = false;
+ return false;
+ }
+
+ int position = voiceInfo.NextTrackPosition;
+
+ voiceInfo.TicksLeftForNextTrackCommand--;
+
+ if (voiceInfo.TicksLeftForNextTrackCommand != 0)
+ {
+ if (voiceInfo.Track[position] >= 0x80)
+ ParseTrackEffect(voiceInfo, ref position);
+
+ voiceInfo.NextTrackPosition = position;
+ return false;
+ }
+
+ for (;;)
+ {
+ if (voiceInfo.Track[position] < 0x80)
+ break;
+
+ ParseTrackEffect(voiceInfo, ref position);
+
+ if (voiceInfo.SwitchToNextPosition)
+ {
+ if (features.CheckForTicks && (voiceInfo.TicksLeftForNextTrackCommand == 0))
+ voiceInfo.TicksLeftForNextTrackCommand = 1;
+
+ return true;
+ }
+ }
+
+ byte note = voiceInfo.Track[position++];
+
+ if (note == 0x7f)
+ {
+ voiceInfo.TicksLeftForNextTrackCommand = voiceInfo.Track[position++];
+ voiceInfo.NextTrackPosition = position;
+
+ return false;
+ }
+
+ playbackInfo.PortamentoAddValue = 0;
+
+ note = (byte)(note + voiceInfo.Transpose);
+ voiceInfo.TransposedNote = note;
+
+ if (features.EnablePortamento)
+ {
+ voiceInfo.PortamentoControlFlag = voiceInfo.Portamento1Enabled;
+ if (voiceInfo.PortamentoControlFlag != 0)
+ {
+ voiceInfo.PortamentoStartDelayCounter = voiceInfo.PortamentoStartDelay;
+ voiceInfo.PortamentoDurationCounter = voiceInfo.PortamentoDuration;
+ voiceInfo.PortamentoAddValue = Tables.FineTune[Tables.FineTuneStartIndex - voiceInfo.PortamentoDeltaNoteNumber];
+ }
+ }
+
+ if (features.EnableVolumeFade)
+ {
+ voiceInfo.VolumeFadeRunning = voiceInfo.VolumeFadeEnabled;
+ if (voiceInfo.VolumeFadeRunning)
+ {
+ voiceInfo.VolumeFadeSpeed = voiceInfo.VolumeFadeInitSpeed;
+ voiceInfo.VolumeFadeSpeedCounter = voiceInfo.VolumeFadeInitSpeed;
+ voiceInfo.VolumeFadeDurationCounter = voiceInfo.VolumeFadeDuration;
+ voiceInfo.VolumeFadeAddValue = voiceInfo.VolumeFadeInitAddValue;
+ voiceInfo.VolumeFadeValue = 0;
+ }
+ }
+
+ byte ticks = voiceInfo.Track[position++];
+ if (features.ExtraTickArg && (ticks == 0))
+ {
+ voiceInfo.TicksLeftForNextTrackCommand = voiceInfo.Track[position++];
+ ticks = 0xff;
+ }
+ else
+ voiceInfo.TicksLeftForNextTrackCommand = ticks;
+
+ if (features.EnablePortamento && voiceInfo.Portamento2Enabled)
+ {
+ voiceInfo.PortamentoControlFlag = 0xff;
+ voiceInfo.PortamentoStartDelayCounter = 0;
+ voiceInfo.PortamentoDurationCounter = voiceInfo.PortamentoDuration;
+
+ byte note1 = note;
+ if (!voiceInfo.UseNewNote)
+ note = voiceInfo.PreviousTransposedNote;
+
+ voiceInfo.TransposedNote = note;
+ voiceInfo.PortamentoAddValue = Tables.FineTune[Tables.FineTuneStartIndex - (note1 - note)];
+ }
+
+ voiceInfo.PreviousTransposedNote = voiceInfo.TransposedNote;
+ voiceInfo.NextTrackPosition = position;
+ voiceInfo.UseNewNote = false;
+
+ Sample sample = voiceInfo.SampleInfo;
+ voiceInfo.SampleInfo2 = sample;
+
+ short volume = features.EnableVolumeFade ? (short)((sample.Volume * voiceInfo.ChannelVolume) / 16384) : (short)sample.Volume;
+ SetupSample(playbackInfo, channel, sample, note, ticks, volume, voiceInfo.ChannelVolumeSlideSpeed, voiceInfo.ChannelVolumeSlideAddValue);
+
+ return false;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Parse track effect
+ ///
+ /********************************************************************/
+ private void ParseTrackEffect(VoiceInfo voiceInfo, ref int position)
+ {
+ byte cmd = voiceInfo.Track[position++];
+
+ if (cmd <= features.MaxSampleMappingValue)
+ {
+ int index = features.GetSampleMappingVersion == 1 ? cmd & 0x07 : cmd - 0x80;
+ voiceInfo.SampleInfo = samples[voiceInfo.SampleMapping[index]];
+ }
+ else if (cmd == 0xff)
+ {
+ voiceInfo.SwitchToNextPosition = true;
+ position--;
+ }
+ else if ((features.UsesCxTrackEffects && (cmd < 0xc0)) || (features.Uses9xTrackEffects && (cmd < 0x9b)))
+ {
+ // Set control flag, which is not used
+ }
+ else
+ {
+ switch (cmd)
+ {
+ case 0xc0 when features.UsesCxTrackEffects && features.EnablePortamento:
+ case 0x9b when features.Uses9xTrackEffects && features.EnablePortamento:
+ {
+ voiceInfo.Portamento1Enabled = 255;
+ voiceInfo.Portamento2Enabled = false;
+ voiceInfo.PortamentoStartDelay = voiceInfo.Track[position++];
+ voiceInfo.PortamentoDuration = voiceInfo.Track[position++];
+ voiceInfo.PortamentoDeltaNoteNumber = (sbyte)voiceInfo.Track[position++];
+ break;
+ }
+
+ case 0xc1 when features.UsesCxTrackEffects && features.EnablePortamento:
+ case 0x9c when features.Uses9xTrackEffects && features.EnablePortamento:
+ {
+ voiceInfo.Portamento1Enabled = 0;
+ break;
+ }
+
+ case 0xc2 when features.UsesCxTrackEffects && features.EnableVolumeFade:
+ case 0x9d when features.Uses9xTrackEffects && features.EnableVolumeFade:
+ {
+ voiceInfo.VolumeFadeEnabled = true;
+ voiceInfo.VolumeFadeInitSpeed = voiceInfo.Track[position++];
+ voiceInfo.VolumeFadeDuration = voiceInfo.Track[position++];
+ voiceInfo.VolumeFadeInitAddValue = (sbyte)voiceInfo.Track[position++];
+ break;
+ }
+
+ case 0xc3 when features.UsesCxTrackEffects && features.EnableVolumeFade:
+ case 0x9e when features.Uses9xTrackEffects && features.EnableVolumeFade:
+ {
+ voiceInfo.VolumeFadeEnabled = false;
+ break;
+ }
+
+ case 0xc4 when features.UsesCxTrackEffects && features.EnablePortamento:
+ case 0x9f when features.Uses9xTrackEffects && features.EnablePortamento:
+ {
+ voiceInfo.Portamento2Enabled = true;
+ voiceInfo.Portamento1Enabled = 0;
+ voiceInfo.PortamentoDuration = voiceInfo.Track[position++];
+ break;
+ }
+
+ case 0xc5 when features.UsesCxTrackEffects && features.EnablePortamento:
+ case 0xa0 when features.Uses9xTrackEffects && features.EnablePortamento:
+ {
+ voiceInfo.Portamento2Enabled = false;
+ break;
+ }
+
+ case 0xc6 when features.UsesCxTrackEffects && features.EnableVolumeFade:
+ case 0xa1 when features.Uses9xTrackEffects && features.EnableVolumeFade:
+ {
+ voiceInfo.ChannelVolume = (ushort)((voiceInfo.Track[position++] << 8) | 0xff);
+
+ if (features.EnableFinalVolumeSlide)
+ {
+ voiceInfo.ChannelVolumeSlideSpeed = voiceInfo.Track[position++];
+ voiceInfo.ChannelVolumeSlideAddValue = (sbyte)voiceInfo.Track[position++];
+ }
+ break;
+ }
+
+ case 0xc7 when features.UsesCxTrackEffects && features.EnableFinalVolumeSlide:
+ case 0xa2 when features.Uses9xTrackEffects && features.EnableFinalVolumeSlide:
+ {
+ voiceInfo.ChannelVolumeSlideSpeed = 0;
+ voiceInfo.ChannelVolume = 0xffff;
+ break;
+ }
+ }
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Will setup a channel to play a sample
+ ///
+ /********************************************************************/
+ private void SetupSample(VoicePlaybackInfo playbackInfo, IChannel channel, Sample sample, byte transposedNote, byte playTicks, short volume, ushort volumeSlideSpeed, short volumeSlideAddValue)
+ {
+ channel.Mute();
+
+ playbackInfo.DmaEnabled = false;
+ playbackInfo.SampleNumber = sample.SampleNumber;
+ playbackInfo.SampleData = sample.SampleData;
+ playbackInfo.SampleLength = sample.Length;
+
+ playbackInfo.PlayingSample = sample;
+ playbackInfo.SamplePlayTicksCounter = playTicks;
+
+ int periodIndex = -(transposedNote & 0x7f) + sample.NoteTranspose + Tables.FineTuneStartIndex;
+ playbackInfo.NotePeriod = periodIndex >= 0 ? (ushort)(((((Tables.FineTune[periodIndex] & 0xffff) * sample.FineTunePeriod) >> 16) + ((Tables.FineTune[periodIndex] >> 16) * sample.FineTunePeriod)) & 0xffff) : (ushort)0;
+
+ playbackInfo.SamplePortamentoDuration = sample.PortamentoDuration;
+
+ if (playbackInfo.SamplePortamentoDuration >= 0)
+ {
+ playbackInfo.SampleVibratoDepth = (ushort)(sample.VibratoDepth / 2);
+ if ((sample.VibratoDepth & 1) != 0)
+ playbackInfo.SampleVibratoDepth++;
+
+ playbackInfo.SamplePortamentoAddValue = (short)((sample.PortamentoAddValue * playbackInfo.NotePeriod) / 32768);
+ playbackInfo.SampleVibratoAddValue = (short)((sample.VibratoAddValue * playbackInfo.NotePeriod) / 32768);
+ }
+
+ playbackInfo.SamplePeriodAddValue = 0;
+
+ playbackInfo.HandleSampleCallback = sample.VolumeFadeSpeed == 0 ? HandleSamplePlayOnce : HandleSampleLoop;
+
+ playbackInfo.FinalVolume = volume;
+
+ if (volume > playingInfo.MasterVolume)
+ volume = playingInfo.MasterVolume;
+
+ channel.SetAmigaVolume((ushort)volume);
+
+ if (features.EnableFinalVolumeSlide)
+ {
+ playbackInfo.FinalVolumeSlideSpeed = volumeSlideSpeed;
+ playbackInfo.FinalVolumeSlideSpeedCounter = volumeSlideSpeed;
+ playbackInfo.FinalVolumeSlideAddValue = volumeSlideAddValue;
+ }
+
+ playbackInfo.LoopDelayCounter = 2;
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Handle realtime effects for all channels
+ ///
+ /********************************************************************/
+ private void HandleEffects()
+ {
+ if ((features.MasterVolumeFadeVersion > 0))
+ DoMasterVolumeFade();
+
+ for (int i = 0; i < 4; i++)
+ {
+ VoicePlaybackInfo playbackInfo = voicePlaybackInfo[i];
+ IChannel channel = VirtualChannels[i];
+
+ HandleVoiceEffects(playbackInfo, channel);
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Handle realtime effects for a single channel
+ ///
+ /********************************************************************/
+ private void HandleVoiceEffects(VoicePlaybackInfo playbackInfo, IChannel channel)
+ {
+ if (playbackInfo.LoopDelayCounter != 0)
+ {
+ Sample sample = playbackInfo.PlayingSample;
+
+ if (playbackInfo.SamplePlayTicksCounter > 0)
+ playbackInfo.SamplePlayTicksCounter--;
+
+ if (features.EnableSampleEffects)
+ DoSampleEffects(playbackInfo, sample);
+
+ if (features.EnableFinalVolumeSlide)
+ DoFinalVolumeSlide(playbackInfo);
+
+ playbackInfo.HandleSampleCallback(playbackInfo, channel, sample);
+
+ short volume = playbackInfo.FinalVolume;
+ if (volume > 0)
+ {
+ if (volume > playingInfo.MasterVolume)
+ volume = playingInfo.MasterVolume;
+
+ if (volume < 0)
+ volume = 0;
+
+ channel.SetAmigaVolume((ushort)volume);
+
+ ushort period = (ushort)(playbackInfo.NotePeriod + playbackInfo.SamplePeriodAddValue + playbackInfo.PortamentoAddValue);
+ channel.SetAmigaPeriod(period);
+
+ if (!features.SetDmaInSampleHandlers)
+ EnableDma(playbackInfo, channel);
+ }
+ else
+ {
+ channel.Mute();
+
+ playbackInfo.LoopDelayCounter = 0;
+ }
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Will simulate Amiga Hardware when DMA is enabled
+ ///
+ /********************************************************************/
+ private void EnableDma(VoicePlaybackInfo playbackInfo, IChannel channel)
+ {
+ if (!playbackInfo.DmaEnabled)
+ {
+ uint sampleLength = playbackInfo.SampleLength * 2U;
+
+ channel.PlaySample(playbackInfo.SampleNumber, playbackInfo.SampleData, 0, sampleLength);
+ channel.SetLoop(0, sampleLength); // The Amiga hardware always loops the sample
+
+ playbackInfo.DmaEnabled = true;
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Will simulate Amiga Hardware to loop a sample and do volume fade
+ ///
+ /********************************************************************/
+ private void HandleSampleLoop(VoicePlaybackInfo playbackInfo, IChannel channel, Sample sample)
+ {
+ if (playbackInfo.LoopDelayCounter < 0x8000)
+ {
+ if (features.SetDmaInSampleHandlers)
+ EnableDma(playbackInfo, channel);
+
+ playbackInfo.LoopDelayCounter--;
+
+ if (playbackInfo.LoopDelayCounter == 0)
+ {
+ playbackInfo.LoopDelayCounter = (ushort)~playbackInfo.LoopDelayCounter;
+
+ uint loopLength = sample.LoopLength * 2U;
+ if (loopLength > 0)
+ {
+ channel.SetSample(sample.LoopOffset, loopLength);
+ channel.SetLoop(sample.LoopOffset, loopLength);
+ }
+ else
+ channel.Mute();
+ }
+ }
+ else
+ {
+ if (playbackInfo.SamplePlayTicksCounter <= 1)
+ playbackInfo.HandleSampleCallback = HandleSampleVolumeFade;
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Will do a volume fade on a sample
+ ///
+ /********************************************************************/
+ private void HandleSampleVolumeFade(VoicePlaybackInfo playbackInfo, IChannel channel, Sample sample)
+ {
+ playbackInfo.LoopDelayCounter = 0x8000;
+ playbackInfo.FinalVolume = (short)(playbackInfo.FinalVolume + sample.VolumeFadeSpeed);
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Will simulate Amiga Hardware to only play the sample once
+ ///
+ /********************************************************************/
+ private void HandleSamplePlayOnce(VoicePlaybackInfo playbackInfo, IChannel channel, Sample sample)
+ {
+ if (playbackInfo.LoopDelayCounter < 0x8000)
+ {
+ playbackInfo.LoopDelayCounter--;
+
+ if (playbackInfo.LoopDelayCounter == 0)
+ {
+ playbackInfo.LoopDelayCounter = (ushort)~playbackInfo.LoopDelayCounter;
+
+ // This will simulate stopping the sample when it has played its buffer.
+ // The original player has an Audio IRQ which will stop the DMA when done
+ channel.SetSample(Tables.EmptySample, 0, (uint)Tables.EmptySample.Length);
+ }
+ else
+ {
+ if (features.SetDmaInSampleHandlers)
+ EnableDma(playbackInfo, channel);
+ }
+ }
+ else
+ {
+ if (playbackInfo.SamplePlayTicksCounter == 1)
+ playbackInfo.LoopDelayCounter = 0x8000;
+
+ if (!channel.IsActive)
+ playbackInfo.FinalVolume = 0;
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Handle realtime effects set from the playing sample itself
+ ///
+ /********************************************************************/
+ private void DoSampleEffects(VoicePlaybackInfo playbackInfo, Sample sample)
+ {
+ if (playbackInfo.SamplePortamentoDuration != 0)
+ {
+ if (playbackInfo.SamplePortamentoDuration == -1)
+ return;
+
+ playbackInfo.SamplePeriodAddValue += playbackInfo.SamplePortamentoAddValue;
+
+ playbackInfo.SamplePortamentoDuration--;
+
+ if (playbackInfo.SamplePortamentoDuration != 0)
+ return;
+ }
+
+ playbackInfo.SamplePeriodAddValue += playbackInfo.SampleVibratoAddValue;
+
+ playbackInfo.SampleVibratoDepth--;
+
+ if (playbackInfo.SampleVibratoDepth == 0)
+ {
+ if (sample.VibratoDepth != 0)
+ {
+ playbackInfo.SampleVibratoDepth = sample.VibratoDepth;
+ playbackInfo.SampleVibratoAddValue = (short)-playbackInfo.SampleVibratoAddValue;
+ }
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Handle final volume slide
+ ///
+ /********************************************************************/
+ private void DoFinalVolumeSlide(VoicePlaybackInfo playbackInfo)
+ {
+ if (playbackInfo.FinalVolumeSlideSpeed != 0)
+ {
+ playbackInfo.FinalVolumeSlideSpeedCounter--;
+
+ if (playbackInfo.FinalVolumeSlideSpeedCounter == 0)
+ {
+ playbackInfo.FinalVolumeSlideSpeedCounter = playbackInfo.FinalVolumeSlideSpeed;
+ playbackInfo.FinalVolume = (short)(playbackInfo.FinalVolume + playbackInfo.FinalVolumeSlideAddValue);
+ }
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Handle portamento
+ ///
+ /********************************************************************/
+ private void DoPortamento(VoiceInfo voiceInfo, VoicePlaybackInfo playbackInfo)
+ {
+ if (voiceInfo.PortamentoControlFlag != 0)
+ {
+ if (voiceInfo.PortamentoControlFlag >= 0x80)
+ {
+ int temp = (short)((playbackInfo.NotePeriod * (voiceInfo.PortamentoAddValue & 0xffff) >> 16) + (playbackInfo.NotePeriod * (voiceInfo.PortamentoAddValue >> 16))) - playbackInfo.NotePeriod;
+ voiceInfo.PortamentoAddValue = temp / voiceInfo.PortamentoDurationCounter;
+
+ voiceInfo.PortamentoControlFlag &= 0x7f;
+ }
+
+ if (voiceInfo.PortamentoStartDelayCounter == 0)
+ {
+ if (voiceInfo.PortamentoDurationCounter != 0)
+ {
+ voiceInfo.PortamentoDurationCounter--;
+ playbackInfo.PortamentoAddValue = (short)(playbackInfo.PortamentoAddValue + voiceInfo.PortamentoAddValue);
+ }
+ }
+ else
+ voiceInfo.PortamentoStartDelayCounter--;
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Handle volume fade
+ ///
+ /********************************************************************/
+ private void DoVolumeFade(VoiceInfo voiceInfo, VoicePlaybackInfo playbackInfo, IChannel channel)
+ {
+ if (voiceInfo.VolumeFadeRunning && (voiceInfo.VolumeFadeDurationCounter != 0))
+ {
+ voiceInfo.VolumeFadeSpeedCounter--;
+
+ if (voiceInfo.VolumeFadeSpeedCounter == 0)
+ {
+ voiceInfo.VolumeFadeDurationCounter--;
+
+ voiceInfo.VolumeFadeSpeedCounter = voiceInfo.VolumeFadeSpeed;
+
+ int volume = voiceInfo.VolumeFadeValue + voiceInfo.VolumeFadeAddValue;
+ voiceInfo.VolumeFadeValue = (short)volume;
+
+ volume += voiceInfo.SampleInfo2.Volume;
+ if (volume < 0)
+ voiceInfo.VolumeFadeDurationCounter = 0;
+ else
+ {
+ if (volume > 64)
+ volume = 64;
+
+ SetupSample(playbackInfo, channel, voiceInfo.SampleInfo2, voiceInfo.TransposedNote, voiceInfo.VolumeFadeSpeed, (short)volume, 0, 0);
+ }
+ }
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Handle master volume fade
+ ///
+ /********************************************************************/
+ private void DoMasterVolumeFade()
+ {
+ if (playingInfo.MasterVolumeFadeSpeed != 0)
+ {
+ playingInfo.MasterVolumeFadeSpeedCounter--;
+
+ if (playingInfo.MasterVolumeFadeSpeedCounter == 0)
+ {
+ playingInfo.MasterVolumeFadeSpeedCounter = playingInfo.MasterVolumeFadeSpeed;
+
+ if (features.MasterVolumeFadeVersion == 2)
+ playingInfo.MasterVolumeFadeSpeedCounter--;
+
+ playingInfo.MasterVolume--;
+
+ if (playingInfo.MasterVolume < 0)
+ {
+ playingInfo.EnablePlaying = false;
+
+ for (int i = 0; i < 4; i++)
+ VirtualChannels[i].Mute();
+ }
+ }
+ }
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Will update the module information with current channel positions
+ ///
+ /********************************************************************/
+ private void ShowChannelPositions()
+ {
+ OnModuleInfoChanged(InfoPositionLine, FormatPositions());
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Will update the module information with track numbers
+ ///
+ /********************************************************************/
+ private void ShowTracks()
+ {
+ OnModuleInfoChanged(InfoTrackLine, FormatTracks());
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Will update the module information with all dynamic values
+ ///
+ /********************************************************************/
+ private void UpdateModuleInformation()
+ {
+ ShowChannelPositions();
+ ShowTracks();
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Return a string containing the songs position lengths
+ ///
+ /********************************************************************/
+ private string FormatPositionLengths()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < 4; i++)
+ {
+ sb.Append(voices[i].PositionList.Length);
+ sb.Append(", ");
+ }
+
+ sb.Remove(sb.Length - 2, 2);
+
+ return sb.ToString();
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Return a string containing the playing positions
+ ///
+ /********************************************************************/
+ private string FormatPositions()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < 4; i++)
+ {
+ sb.Append(voices[i].CurrentPosition);
+ sb.Append(", ");
+ }
+
+ sb.Remove(sb.Length - 2, 2);
+
+ return sb.ToString();
+ }
+
+
+
+ /********************************************************************/
+ ///
+ /// Return a string containing the playing tracks
+ ///
+ /********************************************************************/
+ private string FormatTracks()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < 4; i++)
+ {
+ sb.Append(voices[i].PlayingTrack);
+ sb.Append(", ");
+ }
+
+ sb.Remove(sb.Length - 2, 2);
+
+ return sb.ToString();
+ }
+ #endregion
+ }
+}
diff --git a/Source/Agents/Players/BenDaglish/Containers/Features.cs b/Source/Agents/Players/BenDaglish/Containers/Features.cs
new file mode 100644
index 00000000..7525e9fd
--- /dev/null
+++ b/Source/Agents/Players/BenDaglish/Containers/Features.cs
@@ -0,0 +1,33 @@
+/******************************************************************************/
+/* This source, or parts thereof, may be used in any software as long the */
+/* license of NostalgicPlayer is keep. See the LICENSE file for more */
+/* information. */
+/******************************************************************************/
+namespace Polycode.NostalgicPlayer.Agent.Player.BenDaglish.Containers
+{
+ ///
+ /// Holds which features are enabled or not
+ ///
+ internal class Features
+ {
+ public int MasterVolumeFadeVersion; // -1 = None
+ public bool SetDmaInSampleHandlers;
+ public bool EnableCounter;
+ public bool EnableSampleEffects;
+ public bool EnableFinalVolumeSlide;
+ public bool EnableVolumeFade;
+ public bool EnablePortamento;
+ public bool CheckForTicks;
+ public bool ExtraTickArg;
+ public bool Uses9xTrackEffects;
+ public bool UsesCxTrackEffects;
+
+ public byte MaxTrackValue;
+ public bool EnableC0TrackLoop;
+ public bool EnableF0TrackLoop;
+
+ public byte MaxSampleMappingValue;
+ public int GetSampleMappingVersion;
+ public int SetSampleMappingVersion;
+ }
+}
diff --git a/Source/Agents/Players/BenDaglish/Containers/GlobalPlayingInfo.cs b/Source/Agents/Players/BenDaglish/Containers/GlobalPlayingInfo.cs
new file mode 100644
index 00000000..7adfa4ce
--- /dev/null
+++ b/Source/Agents/Players/BenDaglish/Containers/GlobalPlayingInfo.cs
@@ -0,0 +1,33 @@
+/******************************************************************************/
+/* This source, or parts thereof, may be used in any software as long the */
+/* license of NostalgicPlayer is keep. See the LICENSE file for more */
+/* information. */
+/******************************************************************************/
+using Polycode.NostalgicPlayer.Kit.Interfaces;
+
+namespace Polycode.NostalgicPlayer.Agent.Player.BenDaglish.Containers
+{
+ ///
+ /// Holds global information about the playing state
+ ///
+ internal class GlobalPlayingInfo : IDeepCloneable
+ {
+ public bool EnablePlaying;
+
+ public short MasterVolume;
+ public short MasterVolumeFadeSpeed;
+ public short MasterVolumeFadeSpeedCounter;
+
+ public ushort Counter;
+
+ /********************************************************************/
+ ///
+ /// Make a deep copy of the current object
+ ///
+ /********************************************************************/
+ public GlobalPlayingInfo MakeDeepClone()
+ {
+ return (GlobalPlayingInfo)MemberwiseClone();
+ }
+ }
+}
diff --git a/Source/Agents/Players/BenDaglish/Containers/Sample.cs b/Source/Agents/Players/BenDaglish/Containers/Sample.cs
new file mode 100644
index 00000000..6a9cda11
--- /dev/null
+++ b/Source/Agents/Players/BenDaglish/Containers/Sample.cs
@@ -0,0 +1,31 @@
+/******************************************************************************/
+/* This source, or parts thereof, may be used in any software as long the */
+/* license of NostalgicPlayer is keep. See the LICENSE file for more */
+/* information. */
+/******************************************************************************/
+namespace Polycode.NostalgicPlayer.Agent.Player.BenDaglish.Containers
+{
+ ///
+ /// Holds information about a single sample
+ ///
+ internal class Sample
+ {
+ public short SampleNumber;
+ public sbyte[] SampleData;
+ public ushort Length;
+ public uint LoopOffset;
+ public ushort LoopLength;
+
+ public ushort Volume;
+ public short VolumeFadeSpeed;
+
+ public short PortamentoDuration;
+ public short PortamentoAddValue;
+
+ public ushort VibratoDepth;
+ public ushort VibratoAddValue;
+
+ public short NoteTranspose;
+ public ushort FineTunePeriod;
+ }
+}
diff --git a/Source/Agents/Players/BenDaglish/Containers/Snapshot.cs b/Source/Agents/Players/BenDaglish/Containers/Snapshot.cs
new file mode 100644
index 00000000..5f0362a5
--- /dev/null
+++ b/Source/Agents/Players/BenDaglish/Containers/Snapshot.cs
@@ -0,0 +1,32 @@
+/******************************************************************************/
+/* This source, or parts thereof, may be used in any software as long the */
+/* license of NostalgicPlayer is keep. See the LICENSE file for more */
+/* information. */
+/******************************************************************************/
+using Polycode.NostalgicPlayer.Kit.Interfaces;
+using Polycode.NostalgicPlayer.Kit.Utility;
+
+namespace Polycode.NostalgicPlayer.Agent.Player.BenDaglish.Containers
+{
+ ///
+ /// Holds all the information about the player state at a specific time
+ ///
+ internal class Snapshot : ISnapshot
+ {
+ public GlobalPlayingInfo PlayingInfo;
+ public VoiceInfo[] Voices;
+ public VoicePlaybackInfo[] PlaybackVoices;
+
+ /********************************************************************/
+ ///
+ /// Constructor
+ ///
+ /********************************************************************/
+ public Snapshot(GlobalPlayingInfo playingInfo, VoiceInfo[] voices, VoicePlaybackInfo[] playbackVoices)
+ {
+ PlayingInfo = playingInfo.MakeDeepClone();
+ Voices = ArrayHelper.CloneObjectArray(voices);
+ PlaybackVoices = ArrayHelper.CloneObjectArray(playbackVoices);
+ }
+ }
+}
diff --git a/Source/Agents/Players/BenDaglish/Containers/SongInfo.cs b/Source/Agents/Players/BenDaglish/Containers/SongInfo.cs
new file mode 100644
index 00000000..a82def26
--- /dev/null
+++ b/Source/Agents/Players/BenDaglish/Containers/SongInfo.cs
@@ -0,0 +1,15 @@
+/******************************************************************************/
+/* This source, or parts thereof, may be used in any software as long the */
+/* license of NostalgicPlayer is keep. See the LICENSE file for more */
+/* information. */
+/******************************************************************************/
+namespace Polycode.NostalgicPlayer.Agent.Player.BenDaglish.Containers
+{
+ ///
+ /// Holds information about a single song
+ ///
+ internal class SongInfo
+ {
+ public ushort[] PositionLists = new ushort[4];
+ }
+}
diff --git a/Source/Agents/Players/BenDaglish/Containers/VoiceInfo.cs b/Source/Agents/Players/BenDaglish/Containers/VoiceInfo.cs
new file mode 100644
index 00000000..c0f5146c
--- /dev/null
+++ b/Source/Agents/Players/BenDaglish/Containers/VoiceInfo.cs
@@ -0,0 +1,81 @@
+/******************************************************************************/
+/* This source, or parts thereof, may be used in any software as long the */
+/* license of NostalgicPlayer is keep. See the LICENSE file for more */
+/* information. */
+/******************************************************************************/
+using Polycode.NostalgicPlayer.Kit.Interfaces;
+using Polycode.NostalgicPlayer.Kit.Utility;
+
+namespace Polycode.NostalgicPlayer.Agent.Player.BenDaglish.Containers
+{
+ ///
+ /// Holds playing information for a single voice
+ ///
+ internal class VoiceInfo : IDeepCloneable
+ {
+ public bool ChannelEnabled;
+
+ public byte[] PositionList;
+ public int CurrentPosition;
+ public int NextPosition;
+
+ public int PlayingTrack;
+ public byte[] Track;
+ public int NextTrackPosition;
+
+ public bool SwitchToNextPosition;
+ public byte TrackLoopCounter;
+ public byte TicksLeftForNextTrackCommand;
+
+ public sbyte Transpose;
+ public byte TransposedNote;
+ public byte PreviousTransposedNote;
+ public bool UseNewNote;
+
+ public byte Portamento1Enabled;
+ public bool Portamento2Enabled;
+ public byte PortamentoStartDelay;
+ public byte PortamentoDuration;
+ public sbyte PortamentoDeltaNoteNumber;
+
+ public byte PortamentoControlFlag;
+ public byte PortamentoStartDelayCounter;
+ public byte PortamentoDurationCounter;
+ public int PortamentoAddValue;
+
+ public bool VolumeFadeEnabled;
+ public byte VolumeFadeInitSpeed;
+ public byte VolumeFadeDuration;
+ public short VolumeFadeInitAddValue;
+
+ public bool VolumeFadeRunning;
+ public byte VolumeFadeSpeed;
+ public byte VolumeFadeSpeedCounter;
+ public byte VolumeFadeDurationCounter;
+ public short VolumeFadeAddValue;
+ public short VolumeFadeValue;
+
+ public ushort ChannelVolume;
+ public ushort ChannelVolumeSlideSpeed;
+ public short ChannelVolumeSlideAddValue;
+
+ public Sample SampleInfo;
+ public Sample SampleInfo2;
+
+ public byte[] SampleMapping = new byte[10];
+
+ /********************************************************************/
+ ///
+ /// Make a deep copy of the current object
+ ///
+ /********************************************************************/
+ public VoiceInfo MakeDeepClone()
+ {
+ VoiceInfo clone = (VoiceInfo)MemberwiseClone();
+
+ clone.SampleMapping = ArrayHelper.CloneArray(SampleMapping);
+
+ return clone;
+ }
+ }
+}
diff --git a/Source/Agents/Players/BenDaglish/Containers/VoicePlaybackInfo.cs b/Source/Agents/Players/BenDaglish/Containers/VoicePlaybackInfo.cs
new file mode 100644
index 00000000..fa7f7869
--- /dev/null
+++ b/Source/Agents/Players/BenDaglish/Containers/VoicePlaybackInfo.cs
@@ -0,0 +1,59 @@
+/******************************************************************************/
+/* This source, or parts thereof, may be used in any software as long the */
+/* license of NostalgicPlayer is keep. See the LICENSE file for more */
+/* information. */
+/******************************************************************************/
+using Polycode.NostalgicPlayer.Kit.Interfaces;
+
+namespace Polycode.NostalgicPlayer.Agent.Player.BenDaglish.Containers
+{
+ ///
+ /// Holds playback information for a single voice
+ ///
+ internal class VoicePlaybackInfo : IDeepCloneable
+ {
+ public delegate void HandleSample(VoicePlaybackInfo playbackInfo, IChannel channel, Sample sample);
+
+ public Sample PlayingSample;
+ public byte SamplePlayTicksCounter;
+
+ public ushort NotePeriod;
+ public short FinalVolume;
+
+ public ushort FinalVolumeSlideSpeed;
+ public ushort FinalVolumeSlideSpeedCounter;
+ public short FinalVolumeSlideAddValue;
+
+ public ushort LoopDelayCounter;
+
+ public short PortamentoAddValue;
+
+ public short SamplePortamentoDuration;
+ public short SamplePortamentoAddValue;
+
+ public ushort SampleVibratoDepth;
+ public short SampleVibratoAddValue;
+
+ public short SamplePeriodAddValue;
+
+ public HandleSample HandleSampleCallback;
+
+ // These are used to hold the Amiga hardware registers.
+ // It is needed, because the original player sets the hardware
+ // registers but will first enable the DMA on the next frame/tick
+ public bool DmaEnabled;
+ public short SampleNumber;
+ public sbyte[] SampleData;
+ public ushort SampleLength;
+
+ /********************************************************************/
+ ///
+ /// Make a deep copy of the current object
+ ///
+ /********************************************************************/
+ public VoicePlaybackInfo MakeDeepClone()
+ {
+ return (VoicePlaybackInfo)MemberwiseClone();
+ }
+ }
+}
diff --git a/Source/Agents/Players/BenDaglish/Resources.Designer.cs b/Source/Agents/Players/BenDaglish/Resources.Designer.cs
new file mode 100644
index 00000000..2fecec2e
--- /dev/null
+++ b/Source/Agents/Players/BenDaglish/Resources.Designer.cs
@@ -0,0 +1,195 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Polycode.NostalgicPlayer.Agent.Player.BenDaglish {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Polycode.NostalgicPlayer.Agent.Player.BenDaglish.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Original player by Colin Dooley made for Ben Daglish.
+ ///Converted to C# by Thomas Neumann.
+ ///
+ ///This player can load all the different versions of Ben Daglish players and extract and play the module information from them..
+ ///
+ internal static string IDS_BD_DESCRIPTION {
+ get {
+ return ResourceManager.GetString("IDS_BD_DESCRIPTION", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Original player by Colin Dooley made for Ben Daglish.
+ ///Converted to C# by Thomas Neumann.
+ ///
+ ///This format is not a standard file format like other modules. The music data are embedded into an assembler player, so the whole module file contains both the player and music. This player can recognize all the different versions of Ben Daglishs player and extract the data and play them back..
+ ///
+ internal static string IDS_BD_DESCRIPTION_AGENT1 {
+ get {
+ return ResourceManager.GetString("IDS_BD_DESCRIPTION_AGENT1", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Can't read position lists.
+ ///
+ internal static string IDS_BD_ERR_LOADING_POSITION_LISTS {
+ get {
+ return ResourceManager.GetString("IDS_BD_ERR_LOADING_POSITION_LISTS", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Can't read the sample information.
+ ///
+ internal static string IDS_BD_ERR_LOADING_SAMPLEINFO {
+ get {
+ return ResourceManager.GetString("IDS_BD_ERR_LOADING_SAMPLEINFO", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Can't read the module samples.
+ ///
+ internal static string IDS_BD_ERR_LOADING_SAMPLES {
+ get {
+ return ResourceManager.GetString("IDS_BD_ERR_LOADING_SAMPLES", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Can't read the sub-song information.
+ ///
+ internal static string IDS_BD_ERR_LOADING_SUBSONG {
+ get {
+ return ResourceManager.GetString("IDS_BD_ERR_LOADING_SUBSONG", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Can't read module tracks.
+ ///
+ internal static string IDS_BD_ERR_LOADING_TRACKS {
+ get {
+ return ResourceManager.GetString("IDS_BD_ERR_LOADING_TRACKS", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Number of positions:.
+ ///
+ internal static string IDS_BD_INFODESCLINE0 {
+ get {
+ return ResourceManager.GetString("IDS_BD_INFODESCLINE0", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Used tracks:.
+ ///
+ internal static string IDS_BD_INFODESCLINE1 {
+ get {
+ return ResourceManager.GetString("IDS_BD_INFODESCLINE1", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Used samples:.
+ ///
+ internal static string IDS_BD_INFODESCLINE2 {
+ get {
+ return ResourceManager.GetString("IDS_BD_INFODESCLINE2", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Playing positions:.
+ ///
+ internal static string IDS_BD_INFODESCLINE3 {
+ get {
+ return ResourceManager.GetString("IDS_BD_INFODESCLINE3", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Playing tracks:.
+ ///
+ internal static string IDS_BD_INFODESCLINE4 {
+ get {
+ return ResourceManager.GetString("IDS_BD_INFODESCLINE4", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Ben Daglish.
+ ///
+ internal static string IDS_BD_NAME {
+ get {
+ return ResourceManager.GetString("IDS_BD_NAME", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Ben Daglish.
+ ///
+ internal static string IDS_BD_NAME_AGENT1 {
+ get {
+ return ResourceManager.GetString("IDS_BD_NAME_AGENT1", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Source/Agents/Players/BenDaglish/Resources.resx b/Source/Agents/Players/BenDaglish/Resources.resx
new file mode 100644
index 00000000..6337137e
--- /dev/null
+++ b/Source/Agents/Players/BenDaglish/Resources.resx
@@ -0,0 +1,168 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Original player by Colin Dooley made for Ben Daglish.
+Converted to C# by Thomas Neumann.
+
+This player can load all the different versions of Ben Daglish players and extract and play the module information from them.
+
+
+ Original player by Colin Dooley made for Ben Daglish.
+Converted to C# by Thomas Neumann.
+
+This format is not a standard file format like other modules. The music data are embedded into an assembler player, so the whole module file contains both the player and music. This player can recognize all the different versions of Ben Daglishs player and extract the data and play them back.
+
+
+ Can't read position lists
+
+
+ Can't read the sample information
+
+
+ Can't read the module samples
+
+
+ Can't read the sub-song information
+
+
+ Can't read module tracks
+
+
+ Number of positions:
+
+
+ Used tracks:
+
+
+ Used samples:
+
+
+ Playing positions:
+
+
+ Playing tracks:
+
+
+ Ben Daglish
+
+
+ Ben Daglish
+
+
\ No newline at end of file
diff --git a/Source/Agents/Players/BenDaglish/Tables.cs b/Source/Agents/Players/BenDaglish/Tables.cs
new file mode 100644
index 00000000..17894fb6
--- /dev/null
+++ b/Source/Agents/Players/BenDaglish/Tables.cs
@@ -0,0 +1,44 @@
+/******************************************************************************/
+/* This source, or parts thereof, may be used in any software as long the */
+/* license of NostalgicPlayer is keep. See the LICENSE file for more */
+/* information. */
+/******************************************************************************/
+namespace Polycode.NostalgicPlayer.Agent.Player.BenDaglish
+{
+ ///
+ /// Different tables needed
+ ///
+ internal static class Tables
+ {
+ public const int FineTuneStartIndex = 4 * 12;
+
+ /********************************************************************/
+ ///
+ /// Periods
+ ///
+ /********************************************************************/
+ public static readonly int[] FineTune =
+ [
+ 0x1000, 0x10F3, 0x11F5, 0x1306, 0x1429, 0x155B, 0x16A0, 0x17F9, 0x1965, 0x1AE9, 0x1C82, 0x1E34,
+ 0x2000, 0x21E7, 0x23EB, 0x260D, 0x2851, 0x2AB7, 0x2D41, 0x2FF2, 0x32CC, 0x35D1, 0x3904, 0x3C68,
+ 0x4000, 0x43CE, 0x47D6, 0x4C1B, 0x50A2, 0x556E, 0x5A82, 0x5FE4, 0x6597, 0x6BA2, 0x7208, 0x78D0,
+ 0x8000, 0x879C, 0x8FAC, 0x9837, 0xA145, 0xAADC, 0xB504, 0xBFC8, 0xCB2F, 0xD744, 0xE411, 0xF1A1,
+
+ 0x10000, 0x10F38, 0x11F59, 0x1306F, 0x1428A, 0x155B8, 0x16A09, 0x17F91, 0x1965F, 0x1AE89, 0x1C823, 0x1E343,
+ 0x20000, 0x21E71, 0x23EB3, 0x260DF, 0x28514, 0x2AB70, 0x2D413, 0x2FF22, 0x32CBF, 0x35D13, 0x39047, 0x3C686,
+ 0x40000, 0x43CE3, 0x47D66, 0x4C1BF, 0x50A28, 0x556E0, 0x5A827, 0x5FE44, 0x6597F, 0x6BA27, 0x7208F, 0x78D0D,
+ 0x80000, 0x879C7, 0x8FACD, 0x9837F, 0xA1451, 0xAADC0, 0xB504F, 0xBFC88, 0xCB2FF, 0xD7450, 0xE411F, 0xF1A1B,
+ 0x100000, 0x10F38F, 0x11F59A, 0x1307B2, 0x1428A2, 0x155B81, 0x16A09E, 0x17F910, 0x1965FE, 0x1AE8A0, 0x1C823E, 0x1E3438,
+ 0x200000, 0x21E71F, 0x23EB35, 0x260DFC, 0x285145, 0x2AB702, 0x2D413C, 0x2FF221, 0x32CBFD, 0x35D13F, 0x39047C, 0x3C6870
+ ];
+
+
+
+ /********************************************************************/
+ ///
+ /// Used to stop playing a sample
+ ///
+ /********************************************************************/
+ public static readonly sbyte[] EmptySample = [ 0, 0, 0, 0 ];
+ }
+}
diff --git a/Source/Clients/NostalgicPlayer/NostalgicPlayer.csproj b/Source/Clients/NostalgicPlayer/NostalgicPlayer.csproj
index c158e7c4..ea356e39 100644
--- a/Source/Clients/NostalgicPlayer/NostalgicPlayer.csproj
+++ b/Source/Clients/NostalgicPlayer/NostalgicPlayer.csproj
@@ -115,6 +115,7 @@
+
diff --git a/Source/Clients/NostalgicPlayerConsole/NostalgicPlayerConsole.csproj b/Source/Clients/NostalgicPlayerConsole/NostalgicPlayerConsole.csproj
index 60765a48..b16ba3b7 100644
--- a/Source/Clients/NostalgicPlayerConsole/NostalgicPlayerConsole.csproj
+++ b/Source/Clients/NostalgicPlayerConsole/NostalgicPlayerConsole.csproj
@@ -39,6 +39,7 @@
+
diff --git a/Source/NostalgicPlayer.sln b/Source/NostalgicPlayer.sln
index 9c558a41..dceac3aa 100644
--- a/Source/NostalgicPlayer.sln
+++ b/Source/NostalgicPlayer.sln
@@ -184,6 +184,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonicArranger", "Agents\Pla
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalSoundStudio", "Agents\Players\DigitalSoundStudio\DigitalSoundStudio.csproj", "{2E078AD6-3F07-4899-B8F4-028B21B3C1EF}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenDaglish", "Agents\Players\BenDaglish\BenDaglish.csproj", "{AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1438,6 +1440,22 @@ Global
{2E078AD6-3F07-4899-B8F4-028B21B3C1EF}.Release|x64.Build.0 = Release|Any CPU
{2E078AD6-3F07-4899-B8F4-028B21B3C1EF}.Release|x86.ActiveCfg = Release|Any CPU
{2E078AD6-3F07-4899-B8F4-028B21B3C1EF}.Release|x86.Build.0 = Release|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Debug|x64.Build.0 = Debug|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Debug|x86.Build.0 = Debug|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Release|ARM64.Build.0 = Release|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Release|x64.ActiveCfg = Release|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Release|x64.Build.0 = Release|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Release|x86.ActiveCfg = Release|Any CPU
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1525,6 +1543,7 @@ Global
{6CB7455A-C1F7-4F99-B7C8-9C1591E14546} = {76C2DEA4-D72F-4931-9ACA-B2DDC33BDE98}
{90FEAE9C-1A93-466B-938B-19AA95D867D6} = {76C2DEA4-D72F-4931-9ACA-B2DDC33BDE98}
{2E078AD6-3F07-4899-B8F4-028B21B3C1EF} = {76C2DEA4-D72F-4931-9ACA-B2DDC33BDE98}
+ {AF1EA1FC-76DD-49D5-837A-C08FBBC02C2F} = {76C2DEA4-D72F-4931-9ACA-B2DDC33BDE98}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EA2EFEF5-9B3B-46F2-BE4B-077ED6CADA79}
diff --git a/Source/NostalgicPlayerPackage/Package.appxmanifest b/Source/NostalgicPlayerPackage/Package.appxmanifest
index ec198b7a..eb4e2647 100644
--- a/Source/NostalgicPlayerPackage/Package.appxmanifest
+++ b/Source/NostalgicPlayerPackage/Package.appxmanifest
@@ -1181,6 +1181,14 @@
Digital Sound Studio
+
+
+
+ .bd
+
+ Ben Daglish
+
+
|