public override async Task <IOutputStream> Load(PlaylistItem playlistItem, bool immidiate) { if (!this.IsStarted) { await this.Start().ConfigureAwait(false); } //TODO: Why do we do this? Multiple streams for the same file are valid. if (this.StreamFactory.HasActiveStream(playlistItem.FileName)) { Logger.Write(this, LogLevel.Warn, "The stream is already loaded: {0} => {1}", playlistItem.Id, playlistItem.FileName); } Logger.Write(this, LogLevel.Debug, "Loading stream: {0} => {1}", playlistItem.Id, playlistItem.FileName); var flags = BassFlags.Default; if (this.Float) { flags |= BassFlags.Float; } var stream = this.StreamFactory.CreateInteractiveStream(playlistItem, immidiate, flags); if (stream.IsEmpty) { return(null); } var outputStream = new BassOutputStream(this, this.PipelineManager, stream, playlistItem); outputStream.InitializeComponent(this.Core); this.OnLoaded(outputStream); return(outputStream); }
public override bool Add(BassOutputStream stream) { if (this.Queue.Contains(stream.ChannelHandle)) { Logger.Write(this, LogLevel.Debug, "Stream is already enqueued: {0}", stream.ChannelHandle); return(false); } Logger.Write(this, LogLevel.Debug, "Adding stream to the queue: {0}", stream.ChannelHandle); //If there's nothing in the queue then we're starting. if (this.Queue.Count() == 0) { var flags = default(BassCrossfadeFlags); if (this.Behaviour.Start) { flags = BassCrossfadeFlags.FadeIn; } else { flags = BassCrossfadeFlags.None; } BassUtils.OK(BassCrossfade.ChannelEnqueue(stream.ChannelHandle, flags)); return(true); } BassUtils.OK(BassCrossfade.ChannelEnqueue(stream.ChannelHandle)); return(true); }
public override Task <IOutputStream> Duplicate(IOutputStream stream) { var outputStream = stream as BassOutputStream; if (outputStream.Provider.Flags.HasFlag(BassStreamProviderFlags.Serial)) { Logger.Write(this, LogLevel.Warn, "Cannot duplicate stream for file \"{0}\" with serial provider.", stream.FileName); return(null); } var flags = BassFlags.Default; if (this.Float) { flags |= BassFlags.Float; } var result = this.StreamFactory.CreateBasicStream(stream.PlaylistItem, flags); if (result.IsEmpty) { return(null); } outputStream = new BassOutputStream(this, this.PipelineManager, result, stream.PlaylistItem); outputStream.InitializeComponent(this.Core); #if NET40 return(TaskEx.FromResult <IOutputStream>(outputStream)); #else return(Task.FromResult <IOutputStream>(outputStream)); #endif }
protected virtual bool TryCalculateReplayGain(BassOutputStream stream, out float gain, out float peak, out ReplayGainMode mode) { Logger.Write(this, LogLevel.Debug, "Attempting to calculate track replay gain for file \"{0}\".", stream.FileName); try { var info = default(ReplayGainInfo); if (BassReplayGain.Process(stream.ChannelHandle, out info)) { Logger.Write(this, LogLevel.Debug, "Calculated track replay gain for file \"{0}\": {1}dB", stream.FileName, ReplayGainEffect.GetVolume(info.gain)); gain = info.gain; peak = info.peak; mode = ReplayGainMode.Track; return(true); } else { Logger.Write(this, LogLevel.Warn, "Failed to calculate track replay gain for file \"{0}\".", stream.FileName); } } catch (Exception e) { Logger.Write(this, LogLevel.Warn, "Failed to calculate track replay gain for file \"{0}\": {1}", stream.FileName, e.Message); } gain = 0; peak = 0; mode = ReplayGainMode.None; return(false); }
public override int Position(BassOutputStream stream) { var count = default(int); var channelHandles = BassCrossfade.GetChannels(out count); return(channelHandles.IndexOf(stream.ChannelHandle)); }
protected virtual async Task CreatePipeline(BassOutputStream stream) #endif { if (this.Semaphore == null) { #if NET40 return(TaskEx.FromResult(false)); #else return; #endif } #if NET40 if (!this.Semaphore.Wait(SYNCHRONIZE_PIPELINE_TIMEOUT)) #else if (!await this.Semaphore.WaitAsync(SYNCHRONIZE_PIPELINE_TIMEOUT).ConfigureAwait(false)) #endif { throw new InvalidOperationException(string.Format("{0} is already locked.", this.GetType().Name)); } try { this.CreatePipelineCore(stream); } finally { this.Semaphore.Release(); } #if NET40 return(TaskEx.FromResult(false)); #endif }
protected virtual void CreatePipelineCore(BassOutputStream stream) { this.Pipeline = this.PipelineFactory.CreatePipeline(stream); this.Pipeline.Input.Add(stream); this.Pipeline.Error += this.OnError; this.OnCreated(); }
public static bool Remove(BassOutputStream stream) { Prune(); var reference = default(WeakReference <BassOutputStream>); return(Streams.TryRemove(stream.FileName, out reference)); }
public BassAsioStreamOutput(BassAsioStreamOutputBehaviour behaviour, BassOutputStream stream) : this() { this.Behaviour = behaviour; if (BassUtils.GetChannelDsdRaw(stream.ChannelHandle)) { this.Rate = BassUtils.GetChannelDsdRate(stream.ChannelHandle); this.Flags |= BassFlags.DSDRaw; } else { if (behaviour.Output.Rate == stream.Rate) { this.Rate = stream.Rate; } else if (!behaviour.Output.EnforceRate && BassAsioDevice.Info.SupportedRates.Contains(stream.Rate)) { this.Rate = stream.Rate; } else { Logger.Write(this, LogLevel.Debug, "The requested output rate is either enforced or the device does not support the stream's rate: {0} => {1}", stream.Rate, behaviour.Output.Rate); this.Rate = behaviour.Output.Rate; } if (behaviour.Output.Float) { this.Flags |= BassFlags.Float; } } this.Channels = stream.Channels; }
protected static void OnActiveChanged(BassOutputStream sender) { if (ActiveChanged == null) { return; } ActiveChanged(sender, EventArgs.Empty); }
public BassWasapiStreamOutput(BassWasapiStreamOutputBehaviour behaviour, BassOutputStream stream) : this() { this.Behaviour = behaviour; this.Rate = behaviour.Output.Rate; this.Channels = BassWasapiDevice.Info.Outputs; //WASAPI requires BASS_SAMPLE_FLOAT so don't bother respecting the output's Float setting. this.Flags = BassFlags.Decode | BassFlags.Float; }
protected virtual void Remove(BassOutputStream stream) { var effect = default(ReplayGainEffect); if (this.Effects.TryRemove(stream, out effect)) { effect.Dispose(); } }
public BassReplayGainStreamComponent(BassReplayGainBehaviour behaviour, BassOutputStream stream) { this.Behaviour = behaviour; this.Rate = behaviour.Output.Rate; this.Channels = stream.Channels; this.Flags = BassFlags.Decode; if (this.Behaviour.Output.Float) { this.Flags |= BassFlags.Float; } }
public override bool Add(BassOutputStream stream) { if (this.Queue.Contains(stream.ChannelHandle)) { Logger.Write(this, LogLevel.Debug, "Stream is already enqueued: {0}", stream.ChannelHandle); return(false); } Logger.Write(this, LogLevel.Debug, "Adding stream to the queue: {0}", stream.ChannelHandle); BassUtils.OK(BassGapless.ChannelEnqueue(stream.ChannelHandle)); return(true); }
public BassAsioStreamOutput(BassAsioStreamOutputBehaviour behaviour, BassOutputStream stream) : this() { this.Behaviour = behaviour; this.Rate = behaviour.Output.Rate; this.Channels = BassAsioDevice.Info.Outputs; this.Flags = BassFlags.Decode; if (this.Behaviour.Output.Float) { this.Flags |= BassFlags.Float; } }
public override bool Remove(BassOutputStream stream, Action <BassOutputStream> callBack) { if (!this.Queue.Contains(stream.ChannelHandle)) { Logger.Write(this, LogLevel.Debug, "Stream is not enqueued: {0}", stream.ChannelHandle); return(false); } Logger.Write(this, LogLevel.Debug, "Removing stream from the queue: {0}", stream.ChannelHandle); BassUtils.OK(BassGapless.ChannelRemove(stream.ChannelHandle)); callBack(stream); return(true); }
public static bool Get(string fileName, out BassOutputStream stream) { Prune(); var reference = default(WeakReference <BassOutputStream>); if (Streams.TryGetValue(fileName, out reference)) { stream = reference.Target; return(true); } stream = null; return(false); }
protected virtual async Task WithPipeline(BassOutputStream stream, Action <IBassStreamPipeline> action) { if (this.Pipeline == null) { this.CreatePipelineCore(stream); } else if (!this.Pipeline.Input.CheckFormat(stream)) { Logger.Write(this, LogLevel.Debug, "Current pipeline cannot accept stream, shutting it down: {0}", stream.ChannelHandle); this.FreePipelineCore(); await this.WithPipeline(stream, action).ConfigureAwait(false); return; } action(this.Pipeline); }
public override bool CheckFormat(BassOutputStream stream) { var rate = default(int); var channels = default(int); if (BassUtils.GetChannelDsdRaw(stream.ChannelHandle)) { rate = BassUtils.GetChannelDsdRate(stream.ChannelHandle); channels = BassUtils.GetChannelCount(stream.ChannelHandle); } else { rate = BassUtils.GetChannelPcmRate(stream.ChannelHandle); channels = BassUtils.GetChannelCount(stream.ChannelHandle); } return(this.Rate == rate && this.Channels == channels); }
protected virtual void CreatePipeline(BassOutputStream stream, out IBassStreamInput input, out IEnumerable <IBassStreamComponent> components, out IBassStreamOutput output) { var e = new CreatingPipelineEventArgs(this.QueryPipeline(), stream); this.OnCreatingPipeline(e); input = e.Input; components = e.Components; output = e.Output; if (input == null) { throw new NotImplementedException("Failed to locate a suitable pipeline input."); } if (output == null) { throw new NotImplementedException("Failed to locate a suitable pipeline output."); } }
public override async Task <IOutputStream> Load(PlaylistItem playlistItem, bool immidiate) { if (!this.IsStarted) { await this.Start().ConfigureAwait(false); } Logger.Write(this, LogLevel.Debug, "Loading stream: {0} => {1}", playlistItem.Id, playlistItem.FileName); var stream = await this.StreamFactory.CreateStream(playlistItem, immidiate).ConfigureAwait(false); if (stream.IsEmpty) { return(null); } var outputStream = new BassOutputStream(this, this.PipelineManager, stream.Provider, playlistItem, stream.ChannelHandle); outputStream.InitializeComponent(this.Core); return(outputStream); }
public BassCrossfadeStreamInput(BassCrossfadeStreamInputBehaviour behaviour, BassOutputStream stream) { this.Behaviour = behaviour; this.Channels = stream.Channels; this.Flags = BassFlags.Decode; if (BassUtils.GetChannelDsdRaw(stream.ChannelHandle)) { throw new InvalidOperationException("Cannot apply effects to DSD streams."); } else { this.Rate = stream.Rate; if (behaviour.Output.Float) { this.Flags |= BassFlags.Float; } } }
public BassGaplessStreamInput(BassGaplessStreamInputBehaviour behaviour, BassOutputStream stream) { this.Behaviour = behaviour; this.Channels = stream.Channels; this.Flags = BassFlags.Decode; if (BassUtils.GetChannelDsdRaw(stream.ChannelHandle)) { this.Rate = BassUtils.GetChannelDsdRate(stream.ChannelHandle); this.Flags |= BassFlags.DSDRaw; } else { this.Rate = stream.Rate; if (behaviour.Output.Float) { this.Flags |= BassFlags.Float; } } }
protected virtual void Add(BassOutputStream stream) { if (!FileSystemHelper.IsLocalPath(stream.FileName)) { return; } if (BassUtils.GetChannelDsdRaw(stream.ChannelHandle)) { return; } var gain = default(float); var peak = default(float); var mode = default(ReplayGainMode); if (!this.TryGetReplayGain(stream, out gain, out mode)) { if (this.OnDemand) { //TODO: Bad .Result using (var duplicated = this.Output.Duplicate(stream).Result as BassOutputStream) { if (duplicated == null) { Logger.Write(this, LogLevel.Warn, "Failed to duplicate stream for file \"{0}\", cannot calculate.", stream.FileName); return; } if (!this.TryCalculateReplayGain(duplicated, out gain, out peak, out mode)) { return; } } this.Dispatch(() => this.UpdateMetaData(stream, gain, peak, mode)); } else { return; } } var effect = new ReplayGainEffect(stream.ChannelHandle, gain, mode); effect.Activate(); this.Effects.Add(stream, effect); }
public BassWasapiStreamOutput(BassWasapiStreamOutputBehaviour behaviour, BassOutputStream stream) : this() { this.Behaviour = behaviour; if (behaviour.Output.Rate == stream.Rate) { this.Rate = stream.Rate; } else if (!behaviour.Output.EnforceRate && BassWasapiDevice.Info.SupportedRates.Contains(stream.Rate)) { this.Rate = stream.Rate; } else { Logger.Write(this, LogLevel.Debug, "The requested output rate is either enforced or the device does not support the stream's rate: {0} => {1}", stream.Rate, behaviour.Output.Rate); this.Rate = behaviour.Output.Rate; } this.Channels = BassWasapiDevice.Info.Outputs; //WASAPI requires BASS_SAMPLE_FLOAT so don't bother respecting the output's Float setting. this.Flags = BassFlags.Decode | BassFlags.Float; }
public async Task WithPipelineExclusive(BassOutputStream stream, Action <IBassStreamPipeline> action) { if (this.Semaphore == null) { return; } #if NET40 if (!this.Semaphore.Wait(SYNCHRONIZE_PIPELINE_TIMEOUT)) #else if (!await this.Semaphore.WaitAsync(SYNCHRONIZE_PIPELINE_TIMEOUT).ConfigureAwait(false)) #endif { throw new InvalidOperationException(string.Format("{0} is already locked.", this.GetType().Name)); } try { await this.WithPipeline(stream, action).ConfigureAwait(false); } finally { this.Semaphore.Release(); } }
public IBassStreamPipeline CreatePipeline(BassOutputStream stream) { var input = default(IBassStreamInput); var components = default(IEnumerable <IBassStreamComponent>); var output = default(IBassStreamOutput); this.CreatePipeline(stream, out input, out components, out output); var pipeline = new BassStreamPipeline( input, components, output ); try { pipeline.Connect(); if (Logger.IsDebugEnabled(this)) { if (components.Any()) { Logger.Write( this, LogLevel.Debug, "Connected pipeline: {0}", string.Join(" => ", pipeline.All.Select(component => string.Format("\"{0}\"", component.GetType().Name))) ); } } } catch (Exception e) { Logger.Write(this, LogLevel.Error, "Failed to create pipeline: {0}", e.Message); pipeline.Dispose(); throw; } return(pipeline); }
public override bool Remove(BassOutputStream stream, Action <BassOutputStream> callBack) { if (!this.Queue.Contains(stream.ChannelHandle)) { Logger.Write(this, LogLevel.Debug, "Stream is not enqueued: {0}", stream.ChannelHandle); return(false); } Logger.Write(this, LogLevel.Debug, "Removing stream from the queue: {0}", stream.ChannelHandle); //If there's only one stream in the queue then we're stopping. //Block so the fade out behaviour can be applied before Reset is called. if (this.Queue.Count() == 1) { BassUtils.OK(BassCrossfade.StreamReset(this.Behaviour.Stop)); callBack(stream); return(true); } //Fork so fade out doesn't block the next track being enqueued. this.Dispatch(() => { BassUtils.OK(BassCrossfade.ChannelRemove(stream.ChannelHandle)); callBack(stream); }); return(true); }
protected virtual Task CreatePipeline(BassOutputStream stream)
protected virtual async Task UpdateMetaData(BassOutputStream stream, float gain, float peak, ReplayGainMode mode) { var names = new HashSet <string>(); lock (stream.PlaylistItem.MetaDatas) { var metaDatas = stream.PlaylistItem.MetaDatas.ToDictionary( element => element.Name, StringComparer.OrdinalIgnoreCase ); var metaDataItem = default(MetaDataItem); if (gain != 0) { var name = default(string); switch (mode) { case ReplayGainMode.Album: name = CommonMetaData.ReplayGainAlbumGain; break; case ReplayGainMode.Track: name = CommonMetaData.ReplayGainTrackGain; break; } if (!metaDatas.TryGetValue(name, out metaDataItem)) { metaDataItem = new MetaDataItem(name, MetaDataItemType.Tag); stream.PlaylistItem.MetaDatas.Add(metaDataItem); } metaDataItem.Value = Convert.ToString(gain); names.Add(name); } if (peak != 0) { var name = default(string); switch (mode) { case ReplayGainMode.Album: name = CommonMetaData.ReplayGainAlbumPeak; break; case ReplayGainMode.Track: name = CommonMetaData.ReplayGainTrackPeak; break; } if (!metaDatas.TryGetValue(name, out metaDataItem)) { metaDataItem = new MetaDataItem(name, MetaDataItemType.Tag); stream.PlaylistItem.MetaDatas.Add(metaDataItem); } metaDataItem.Value = Convert.ToString(peak); names.Add(name); } } await this.MetaDataManager.Save( new[] { stream.PlaylistItem }, this.WriteTags, false, names.ToArray() ).ConfigureAwait(false); }