using System; using System.Collections.Generic; using System.Linq; using System.Windows.Input; using System.Windows.Threading; using Chernobyl.App.Core; using Chernobyl.Collections.Generic; using Chernobyl.Extensions; using Chernobyl.Generation; using Chernobyl.Mathematics; using Chernobyl.Mathematics.Space; using GalaSoft.MvvmLight.CommandWpf; namespace Chernobyl.App.Patterns { /// /// Represents the view of an . /// public class PatternControl : ViewModel { /// The to be displayed. /// The instance that public PatternControl(IPattern pattern, IWavePlayerFactory wavePlayerFactory) { _pattern = pattern; _wavePlayerFactory = wavePlayerFactory; // Create the timer that will display the current time of the music. var timeSpan = TimeSpan.FromMilliseconds(50); _dispatcherTimer = new DispatcherTimer(); _dispatcherTimer.Tick += (sender, args) => Seconds += (float)timeSpan.TotalSeconds; _dispatcherTimer.Interval = timeSpan; // Normalize and time the pattern provided so that it sounds like music. const int beatLength = SampleRate / 6; // half second (approximate) //const int beatLength = SampleRate / 3; // full second (approximate) const double minFreq = 200.0; const double maxFreq = 400.0; // Note that we intersperse 0s between each normalized value. This is done to create a // staccato like sound (like that of a drum). _frequency = _pattern.Data .Select(value => value.Normalize(Range.Unit, Tuple.Create(minFreq, maxFreq))) .Intersperse(0) .Select(freq => Tuple.Create(freq, beatLength)); Reset(); // Note: execution method must be a named method due to use of WeakReference, see // RelayCommand for more info. PlayCommand = new RelayCommand(Play, () => _player != null); StopCommand = new RelayCommand(Stop, () => _player != null); ResetCommand = new RelayCommand(Reset); } /// /// Starts the audible playing of the pattern that was given to this instance in the constructor. /// public ICommand PlayCommand { get; } /// /// Stops the audible playing of the pattern that was given to this instance in the constructor. /// public ICommand StopCommand { get; } /// /// Returns the music back to the start. /// public ICommand ResetCommand { get; } /// /// The amount of time that has passed since the beginning of the song. /// public float Seconds { get => _seconds; private set { _seconds = value; RaisePropertyChanged(); } } /// /// The state of the song being played. /// public PlayerState State => _player?.State ?? PlayerState.Stopped; void Play() { _player.Play(); _dispatcherTimer.Start(); RaisePropertyChanged(nameof(State)); } void Stop() { _player.Stop(); _dispatcherTimer.Stop(); RaisePropertyChanged(nameof(State)); } void Reset() { _dispatcherTimer.Stop(); _player?.Dispose(); _player = null; Seconds = 0; const float amplitude = 0.25f; _frequencyEnumerator?.Dispose(); _frequencyEnumerator = _frequency.GetEnumerator(); var square = _frequencyEnumerator.AsFunc(Tuple.Create(0.0, 1)).Square(); var waveFunc = Generation.Audio.CreateSinOscillator(square, () => SampleRate, () => amplitude) .Select(Convert.ToSingle); _player = _wavePlayerFactory.Create(new WavePlayerConfig(waveFunc, SampleRate)); RaisePropertyChanged(nameof(State)); } /// public override void Cleanup() { _player?.Dispose(); _frequencyEnumerator?.Dispose(); } const int SampleRate = 16000; readonly IPattern _pattern; readonly IWavePlayerFactory _wavePlayerFactory; readonly DispatcherTimer _dispatcherTimer; readonly IEnumerable> _frequency; IEnumerator> _frequencyEnumerator; IPlayer _player; float _seconds; } }