/// <summary> /// Initialise the Visualisation Window and Load Decoder/DSP Plugins /// The BASS engine itself is not initialised at this stage, since it may cause S/PDIF for Movies not working on some systems. /// </summary> private void Initialize() { bool result = true; try { Log.Info("BASS: Initialize BASS environment ..."); LoadSettings(); result = BassRegistration.BassRegistration.Register(); if (result) { // BASS_CONFIG_UPDATEPERIOD is set in InitDSDevice in case // we are going to playback over DirectSound. Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0); // Set the Global Volume. 0 = silent, 10000 = Full // We get 0 - 100 from Configuration, so multiply by 100 Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM, Config.StreamVolume * 100); Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_BUFFER, Config.BufferingMs); // network buffersize for webstreams should be larger then the playbackbuffer. // To insure a stable playback-start. int netBufferSize = Config.BufferingMs; // Minimize at default value. if (netBufferSize < 5000) netBufferSize = 5000; Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_NET_BUFFER, netBufferSize); // PreBuffer() takes care of this. Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_NET_PREBUF, 0); } InitializeControls(); Config.LoadAudioDecoderPlugins(); Config.LoadDSPPlugins(); _playBackType = (int)Config.PlayBack; // Create a Stream Copy, if Visualisation is enabled if (HasViz) { Log.Debug("BASS: Create Stream copy for Visualisation"); _streamcopy = new StreamCopy(); _streamcopy.StreamCopyFlags = BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_DECODE; } Log.Info("BASS: Initializing BASS environment done."); _initialized = true; _bassFreed = true; } catch (Exception ex) { Log.Error("BASS: Initialize thread failed. Reason: {0}", ex.Message); } }
/// <summary> /// Starts Playback of the given file /// </summary> /// <param name="filePath"></param> /// <returns></returns> public override bool Play(string filePath) { if (!_Initialized) { return false; } int stream = GetCurrentStream(); bool doFade = false; bool result = true; Speed = 1; // Set playback Speed to normal speed try { if (filePath.ToLower().CompareTo(FilePath.ToLower()) == 0 && stream != 0) { // Selected file is equal to current stream if (_State == PlayState.Paused) { // Resume paused stream if (_SoftStop) { Bass.BASS_ChannelSlideAttribute(stream, BASSAttribute.BASS_ATTRIB_VOL, 1, 500); } else { Bass.BASS_ChannelSetAttribute(stream, BASSAttribute.BASS_ATTRIB_VOL, 1); } result = Bass.BASS_Start(); if (_useASIO) { result = BassAsio.BASS_ASIO_Start(0); } if (result) { _State = PlayState.Playing; if (PlaybackStateChanged != null) { PlaybackStateChanged(this, PlayState.Paused, _State); } } return result; } } else { // Cue support cueTrackStartPos = 0; cueTrackEndPos = 0; if (CueUtil.isCueFakeTrackFile(filePath)) { Log.Debug("BASS: Playing CUE Track: {0}", filePath); currentCueFakeTrackFileName = filePath; CueFakeTrack cueFakeTrack = CueUtil.parseCueFakeTrackFileName(filePath); if (!cueFakeTrack.CueFileName.Equals(currentCueFileName)) { // New CUE. Update chached cue. currentCueSheet = new CueSheet(cueFakeTrack.CueFileName); currentCueFileName = cueFakeTrack.CueFileName; } // Get track start position Track track = currentCueSheet.Tracks[cueFakeTrack.TrackNumber - currentCueSheet.Tracks[0].TrackNumber]; Index index = track.Indices[0]; cueTrackStartPos = CueUtil.cueIndexToFloatTime(index); // If single audio file and is not last track, set track end position. if (currentCueSheet.Tracks[currentCueSheet.Tracks.Length - 1].TrackNumber > track.TrackNumber) { Track nextTrack = currentCueSheet.Tracks[cueFakeTrack.TrackNumber - currentCueSheet.Tracks[0].TrackNumber + 1]; if (nextTrack.DataFile.Filename.Equals(track.DataFile.Filename)) { Index nindex = nextTrack.Indices[0]; cueTrackEndPos = CueUtil.cueIndexToFloatTime(nindex); } } // If audio file is not changed, just set new start/end position and reset pause string audioFilePath = System.IO.Path.GetDirectoryName(cueFakeTrack.CueFileName) + System.IO.Path.DirectorySeparatorChar + track.DataFile.Filename; if (audioFilePath.CompareTo(FilePath) == 0 /* && StreamIsPlaying(stream)*/) { setCueTrackEndPosition(stream); return true; } filePath = audioFilePath; } else { currentCueFileName = null; currentCueSheet = null; } } if (stream != 0 && StreamIsPlaying(stream)) { int oldStream = stream; double oldStreamDuration = GetTotalStreamSeconds(oldStream); double oldStreamElapsedSeconds = GetStreamElapsedTime(oldStream); double crossFadeSeconds = (double)_CrossFadeIntervalMS; if (crossFadeSeconds > 0) crossFadeSeconds = crossFadeSeconds / 1000.0; if ((oldStreamDuration - (oldStreamElapsedSeconds + crossFadeSeconds) > -1)) { FadeOutStop(oldStream); } else { Bass.BASS_ChannelStop(oldStream); } doFade = true; stream = GetNextStream(); if (stream != 0 || StreamIsPlaying(stream)) { FreeStream(stream); } } if (stream != 0) { if (!Stopped) // Check if stopped already to avoid that Stop() is called two or three times { Stop(); } FreeStream(stream); } _State = PlayState.Init; // Make sure Bass is ready to begin playing again Bass.BASS_Start(); float crossOverSeconds = 0; if (_CrossFadeIntervalMS > 0) { crossOverSeconds = (float)_CrossFadeIntervalMS / 1000f; } if (filePath != string.Empty) { // Turn on parsing of ASX files Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_NET_PLAYLIST, 2); // We need different flags for standard BASS and ASIO / Mixing BASSFlag streamFlags; if (_useASIO || _Mixing) { streamFlags = BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT; // Don't use the BASS_STREAM_AUTOFREE flag on a decoding channel. will produce a BASS_ERROR_NOTAVAIL } else { streamFlags = BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_AUTOFREE; } FilePath = filePath; // create the stream _isCDDAFile = false; _isRadio = false; _isLastFMRadio = false; if (Util.Utils.IsCDDA(filePath)) { _isCDDAFile = true; // StreamCreateFile causes problems with Multisession disks, so use StreamCreate with driveindex and track index int driveindex = _cdDriveLetters.IndexOf(filePath.Substring(0, 1)); int tracknum = Convert.ToInt16(filePath.Substring(filePath.IndexOf(".cda") - 2, 2)); stream = BassCd.BASS_CD_StreamCreate(driveindex, tracknum - 1, streamFlags); if (stream == 0) Log.Error("BASS: CD: {0}.", Enum.GetName(typeof (BASSError), Bass.BASS_ErrorGetCode())); } else if (filePath.ToLower().Contains(@"http://") || filePath.ToLower().Contains(@"https://") || filePath.ToLower().StartsWith("mms") || filePath.ToLower().StartsWith("rtsp")) { // We're playing Internet Radio Stream _isLastFMRadio = Util.Utils.IsLastFMStream(filePath); if (!_isLastFMRadio) { // We're playing Internet Radio Stream, but not LastFM _isRadio = true; } stream = Bass.BASS_StreamCreateURL(filePath, 0, streamFlags, null, IntPtr.Zero); if (stream != 0) { // Get the Tags and set the Meta Tag SyncProc _tagInfo = new TAG_INFO(filePath); SetStreamTags(stream); if (BassTags.BASS_TAG_GetFromURL(stream, _tagInfo)) { GetMetaTags(); } Bass.BASS_ChannelSetSync(stream, BASSSync.BASS_SYNC_META, 0, MetaTagSyncProcDelegate, IntPtr.Zero); } Log.Debug("BASSAudio: Webstream found - trying to fetch stream {0}", Convert.ToString(stream)); } else if (IsMODFile(filePath)) { // Load a Mod file stream = Bass.BASS_MusicLoad(filePath, 0, 0, BASSFlag.BASS_SAMPLE_SOFTWARE | BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_MUSIC_AUTOFREE | BASSFlag.BASS_MUSIC_PRESCAN | BASSFlag.BASS_MUSIC_RAMP, 0); } else { // Create a Standard Stream stream = Bass.BASS_StreamCreateFile(filePath, 0, 0, streamFlags); } // Is Mixing / ASIO enabled, then we create a mixer channel and assign the stream to the mixer if ((_Mixing || _useASIO) && stream != 0) { // Do an upmix of the stereo according to the matrix. // Now Plugin the stream to the mixer and set the mixing matrix BassMix.BASS_Mixer_StreamAddChannel(_mixer, stream, BASSFlag.BASS_MIXER_MATRIX | BASSFlag.BASS_STREAM_AUTOFREE | BASSFlag.BASS_MIXER_NORAMPIN | BASSFlag.BASS_MIXER_BUFFER); BassMix.BASS_Mixer_ChannelSetMatrix(stream, _MixingMatrix); } Streams[CurrentStreamIndex] = stream; if (stream != 0) { // When we have a MIDI file, we need to assign the sound banks to the stream if (IsMidiFile(filePath) && soundFonts != null) { BassMidi.BASS_MIDI_StreamSetFonts(stream, soundFonts, soundFonts.Length); } StreamEventSyncHandles[CurrentStreamIndex] = RegisterPlaybackEvents(stream, CurrentStreamIndex); if (doFade && CrossFadeIntervalMS > 0) { _CrossFading = true; // Reduce the stream volume to zero so we can fade it in... Bass.BASS_ChannelSetAttribute(stream, BASSAttribute.BASS_ATTRIB_VOL, 0); // Fade in from 0 to 1 over the _CrossFadeIntervalMS duration Bass.BASS_ChannelSlideAttribute(stream, BASSAttribute.BASS_ATTRIB_VOL, 1, _CrossFadeIntervalMS); } // Attach active DSP effects to the Stream if (_dspActive) { // BASS effects if (_gain != null) { _gain.ChannelHandle = stream; _gain.Start(); } if (_damp != null) { int dampHandle = Bass.BASS_ChannelSetFX(stream, BASSFXType.BASS_FX_BFX_DAMP, _dampPrio); Bass.BASS_FXSetParameters(dampHandle, _damp); } if (_comp != null) { int compHandle = Bass.BASS_ChannelSetFX(stream, BASSFXType.BASS_FX_BFX_COMPRESSOR, _compPrio); Bass.BASS_FXSetParameters(compHandle, _comp); } // VST Plugins foreach (string plugin in _VSTPlugins) { int vstHandle = BassVst.BASS_VST_ChannelSetDSP(stream, plugin, BASSVSTDsp.BASS_VST_DEFAULT, 1); // Copy the parameters of the plugin as loaded on from the settings int vstParm = _vstHandles[plugin]; BassVst.BASS_VST_SetParamCopyParams(vstParm, vstHandle); } // Init Winamp DSP only if we got a winamp plugin actiavtes int waDspPlugin = 0; if (DSP.Settings.Instance.WinAmpPlugins.Count > 0 && !_waDspInitialised) { BassWaDsp.BASS_WADSP_Init(GUIGraphicsContext.ActiveForm); _waDspInitialised = true; foreach (WinAmpPlugin plugins in DSP.Settings.Instance.WinAmpPlugins) { waDspPlugin = BassWaDsp.BASS_WADSP_Load(plugins.PluginDll, 5, 5, 100, 100, null); if (waDspPlugin > 0) { _waDspPlugins[plugins.PluginDll] = waDspPlugin; BassWaDsp.BASS_WADSP_Start(waDspPlugin, 0, 0); } else { Log.Debug("Couldn't load WinAmp Plugin {0}. Error code: {1}", plugins.PluginDll, Enum.GetName(typeof (BASSError), Bass.BASS_ErrorGetCode())); } } } foreach (int waPluginHandle in _waDspPlugins.Values) { BassWaDsp.BASS_WADSP_ChannelSetDSP(waPluginHandle, stream, 1); } } } else { Log.Error("BASS: Unable to create Stream for {0}. Reason: {1}.", filePath, Enum.GetName(typeof (BASSError), Bass.BASS_ErrorGetCode())); } bool playbackStarted = false; if (_Mixing) { if (Bass.BASS_ChannelIsActive(_mixer) == BASSActive.BASS_ACTIVE_PLAYING) { setCueTrackEndPosition(stream); playbackStarted = true; } else { playbackStarted = Bass.BASS_ChannelPlay(_mixer, false); setCueTrackEndPosition(stream); } } else if (_useASIO) { // Get some information about the stream BASS_CHANNELINFO info = new BASS_CHANNELINFO(); Bass.BASS_ChannelGetInfo(stream, info); // In order to provide data for visualisation we need to clone the stream _streamcopy = new StreamCopy(); _streamcopy.ChannelHandle = stream; _streamcopy.StreamFlags = BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT; // decode the channel, so that we have a Streamcopy _asioHandler.Pan = _asioBalance; _asioHandler.Volume = (float)_StreamVolume / 100f; // Set the Sample Rate from the stream _asioHandler.SampleRate = (double)info.freq; // try to set the device rate too (saves resampling) BassAsio.BASS_ASIO_SetRate((double)info.freq); try { _streamcopy.Start(); // start the cloned stream } catch (Exception) { Log.Error("Captured an error on StreamCopy start"); } if (BassAsio.BASS_ASIO_IsStarted()) { setCueTrackEndPosition(stream); playbackStarted = true; } else { BassAsio.BASS_ASIO_Stop(); playbackStarted = BassAsio.BASS_ASIO_Start(0); setCueTrackEndPosition(stream); } } else { setCueTrackEndPosition(stream); playbackStarted = Bass.BASS_ChannelPlay(stream, false); } if (stream != 0 && playbackStarted) { Log.Info("BASS: playback started"); GUIMessage msg = new GUIMessage(GUIMessage.MessageType.GUI_MSG_PLAYBACK_STARTED, 0, 0, 0, 0, 0, null); msg.Label = FilePath; GUIWindowManager.SendThreadMessage(msg); NotifyPlaying = true; NeedUpdate = true; _IsFullScreen = GUIGraphicsContext.IsFullScreenVideo; _VideoPositionX = GUIGraphicsContext.VideoWindow.Left; _VideoPositionY = GUIGraphicsContext.VideoWindow.Top; _VideoWidth = GUIGraphicsContext.VideoWindow.Width; _VideoHeight = GUIGraphicsContext.VideoWindow.Height; // Re-Add the Viswindow to the Mainform Control (It got removed on a manual Stop) SetVisualizationWindow(); SetVideoWindow(); PlayState oldState = _State; _State = PlayState.Playing; if (oldState != _State && PlaybackStateChanged != null) { PlaybackStateChanged(this, oldState, _State); } if (PlaybackStart != null) { PlaybackStart(this, GetTotalStreamSeconds(stream)); } } else { Log.Error("BASS: Unable to play {0}. Reason: {1}.", filePath, Enum.GetName(typeof (BASSError), Bass.BASS_ErrorGetCode())); // Release all of the sync proc handles if (StreamEventSyncHandles[CurrentStreamIndex] != null) { UnregisterPlaybackEvents(stream, StreamEventSyncHandles[CurrentStreamIndex]); } result = false; } } } catch (Exception ex) { result = false; Log.Error("BASS: Play caused an exception: {0}.", ex); } return result; }