From a883de8d23bbbd85b40a817eaa516cd196ecbfb7 Mon Sep 17 00:00:00 2001 From: Brett Story Date: Sun, 31 May 2020 14:50:49 -0500 Subject: [PATCH] Fix: SongPlayer working properly for the first time after the last big refactor. --- Examples/SynthTest/SynthGame.cs | 42 +++++++++++++---------- Framework/Audio/Synthesizer/LiveVoice.cs | 15 ++++++-- Framework/Audio/Synthesizer/Song.cs | 18 ++++++++-- Framework/Audio/Synthesizer/SongPlayer.cs | 4 +-- Framework/Audio/Synthesizer/Voice.cs | 5 +-- 5 files changed, 56 insertions(+), 28 deletions(-) diff --git a/Examples/SynthTest/SynthGame.cs b/Examples/SynthTest/SynthGame.cs index 9c5f8924..8f709819 100644 --- a/Examples/SynthTest/SynthGame.cs +++ b/Examples/SynthTest/SynthGame.cs @@ -20,8 +20,8 @@ protected override void Initialize() { base.Initialize(); this._graphics.IsFullScreen = false; - this._graphics.PreferredBackBufferHeight = 768; - this._graphics.PreferredBackBufferWidth = 1024; + this._graphics.PreferredBackBufferHeight = 1080; + this._graphics.PreferredBackBufferWidth = 1920; this._graphics.ApplyChanges(); this.IsMouseVisible = true; } @@ -62,27 +62,33 @@ private Song CreateSong() { var firstTrack = song.Tracks.First(); firstTrack.Instrument.Oscillator = new SawToothOscillator(); + firstTrack.Instrument.NoteEnvelope.Attack = 50; + firstTrack.Instrument.NoteEnvelope.Release = 50; firstTrack.LeftChannelVolume = 0.25f; firstTrack.RightChannelVolume = 0.75f; - firstTrack.AddNote(0f, 1f, Note.C); - firstTrack.AddNote(1f, 1f, Note.F); - firstTrack.AddNote(2f, 1f, Note.G); - firstTrack.AddNote(3f, 1f, Note.C); + var secondTrack = song.AddTrack(); + secondTrack.Instrument.Oscillator = new SineWaveOscillator(); + secondTrack.Instrument.NoteEnvelope.Attack = 100; + secondTrack.Instrument.NoteEnvelope.Release = 500; + secondTrack.LeftChannelVolume = 0.75f; + secondTrack.RightChannelVolume = 0.25f; - //var secondTrack = song.AddTrack(); - //secondTrack.Instrument.Oscillator = new SineWaveOscillator(); - //secondTrack.LeftChannelVolume = 0.75f; - //secondTrack.RightChannelVolume = 0.25f; + for (var i = 0; i < 4; i++) { + firstTrack.AddNote((4f * i) + 0f, 1f, Note.C); + firstTrack.AddNote((4f * i) + 1f, 1f, Note.F); + firstTrack.AddNote((4f * i) + 2f, 1f, Note.G); + firstTrack.AddNote((4f * i) + 3f, 1f, Note.C); - //firstTrack.AddSlideNote(0f, 0.5f, new Frequency(Note.C, Pitch.Low), new Frequency(Note.C, Pitch.Normal), 1f); - //firstTrack.AddSlideNote(0.5f, 0.5f, new Frequency(Note.C, Pitch.Low), new Frequency(Note.C, Pitch.Normal), 1f); - //firstTrack.AddSlideNote(1f, 0.5f, new Frequency(Note.F, Pitch.Low), new Frequency(Note.F, Pitch.Normal), 1f); - //firstTrack.AddSlideNote(1.5f, 0.5f, new Frequency(Note.F, Pitch.Low), new Frequency(Note.F, Pitch.Normal), 1f); - //firstTrack.AddSlideNote(2f, 0.5f, new Frequency(Note.G, Pitch.Low), new Frequency(Note.G, Pitch.Normal), 1f); - //firstTrack.AddSlideNote(2.5f, 0.5f, new Frequency(Note.G, Pitch.Low), new Frequency(Note.G, Pitch.Normal), 1f); - //firstTrack.AddSlideNote(3f, 0.5f, new Frequency(Note.C, Pitch.Low), new Frequency(Note.C, Pitch.Normal), 1f); - //firstTrack.AddSlideNote(3.5f, 0.5f, new Frequency(Note.C, Pitch.Low), new Frequency(Note.C, Pitch.Normal), 1f); + secondTrack.AddSlideNote((4f * i) + 0f, 0.4f, new Frequency(Note.C, Pitch.Low), new Frequency(Note.C, Pitch.Normal), 1f); + secondTrack.AddSlideNote((4f * i) + 0.5f, 0.4f, new Frequency(Note.C, Pitch.Low), new Frequency(Note.C, Pitch.Normal), 1f); + secondTrack.AddSlideNote((4f * i) + 1f, 0.4f, new Frequency(Note.F, Pitch.Low), new Frequency(Note.F, Pitch.Normal), 1f); + secondTrack.AddSlideNote((4f * i) + 1.5f, 0.4f, new Frequency(Note.F, Pitch.Low), new Frequency(Note.F, Pitch.Normal), 1f); + secondTrack.AddSlideNote((4f * i) + 2f, 0.4f, new Frequency(Note.G, Pitch.Low), new Frequency(Note.G, Pitch.Normal), 1f); + secondTrack.AddSlideNote((4f * i) + 2.5f, 0.4f, new Frequency(Note.G, Pitch.Low), new Frequency(Note.G, Pitch.Normal), 1f); + secondTrack.AddSlideNote((4f * i) + 3f, 0.4f, new Frequency(Note.C, Pitch.High), new Frequency(Note.C, Pitch.Normal), 1f); + secondTrack.AddSlideNote((4f * i) + 3.5f, 0.4f, new Frequency(Note.C, Pitch.High), new Frequency(Note.C, Pitch.Normal), 1f); + } return song; } diff --git a/Framework/Audio/Synthesizer/LiveVoice.cs b/Framework/Audio/Synthesizer/LiveVoice.cs index cd028b0e..efd6564a 100644 --- a/Framework/Audio/Synthesizer/LiveVoice.cs +++ b/Framework/Audio/Synthesizer/LiveVoice.cs @@ -1,22 +1,33 @@ namespace Macabre2D.Framework { + /// + /// A live voice that isn't necesarrily tied to a real note instance. + /// public sealed class LiveVoice : Voice { private bool _isPlaying = false; private ulong _samplesBeforeRelease = 0; + /// + /// Gets the frequency. + /// + /// The frequency. public Frequency Frequency { get { return this.Note.StartFrequency; } } - public override void Reinitialize(Song song, Track track, NoteInstance note, float offset = 0f) { - base.Reinitialize(song, track, note, offset); + /// + public override void Reinitialize(Song song, Track track, NoteInstance note, float startingBeat = 0f) { + base.Reinitialize(song, track, note, startingBeat); this._isPlaying = true; this._samplesBeforeRelease = 0; } + /// + /// Stops this instance. + /// public void Stop() { this._isPlaying = false; this._samplesBeforeRelease = 0; diff --git a/Framework/Audio/Synthesizer/Song.cs b/Framework/Audio/Synthesizer/Song.cs index 5acfaaf6..571d7456 100644 --- a/Framework/Audio/Synthesizer/Song.cs +++ b/Framework/Audio/Synthesizer/Song.cs @@ -41,9 +41,22 @@ public ushort BeatsPerMinute { set { this._beatsPerMinute = value.Clamp(MinimumBeatsPerMinute, MaximummBeatsPerMinute); + this.BeatsPerSecond = this._beatsPerMinute / 60f; } } + /// + /// Gets the beats per second. + /// + /// The beats per second. + public float BeatsPerSecond { get; private set; } = 120f / 60f; + + /// + /// Gets the inverse sample rate. + /// + /// The inverse sample rate. + public float InverseSampleRate { get; private set; } = 1f / MaximumSampleRate; + /// /// Gets the length. /// @@ -66,6 +79,7 @@ public ushort SampleRate { set { this._sampleRate = value.Clamp(MinimumSampleRate, MaximumSampleRate); + this.InverseSampleRate = 1f / this._sampleRate; } } @@ -95,7 +109,7 @@ public Track AddTrack() { /// The beats. /// The number of samples within the beat length. public ulong ConvertBeatsToSamples(float beats) { - return (ulong)Math.Floor((this.SampleRate * 60f * beats) / this.BeatsPerMinute); + return (ulong)Math.Floor((this.SampleRate * beats) / this.BeatsPerSecond); } /// @@ -104,7 +118,7 @@ public ulong ConvertBeatsToSamples(float beats) { /// The number of samples. /// The length of the beat that the number of samples encompass. public float ConvertSamplesToBeats(ushort numberOfSamples) { - return (this.BeatsPerMinute * numberOfSamples) / (this.SampleRate * 60f); + return this.BeatsPerSecond * numberOfSamples * this.InverseSampleRate; } /// diff --git a/Framework/Audio/Synthesizer/SongPlayer.cs b/Framework/Audio/Synthesizer/SongPlayer.cs index bffb85f2..e30bcfc0 100644 --- a/Framework/Audio/Synthesizer/SongPlayer.cs +++ b/Framework/Audio/Synthesizer/SongPlayer.cs @@ -73,6 +73,7 @@ public void Buffer(float volume, ushort numberOfSamples) { if (endOfBuffer > this.Song.Length) { endOfBuffer = this.Song.Length; beatLengthOfBuffer = endOfBuffer - this._currentBeat; + numberOfSamples = (ushort)this.Song.ConvertBeatsToSamples(beatLengthOfBuffer); } var range = new RangeVector(this._currentBeat, endOfBuffer); @@ -80,8 +81,7 @@ public void Buffer(float volume, ushort numberOfSamples) { var notes = track.GetNotes(range); foreach (var note in notes) { - var offset = note.Beat - this._currentBeat; - var voice = this._voicePool.GetNext(this.Song, track, note, offset); + var voice = this._voicePool.GetNext(this.Song, track, note, this._currentBeat); voice.OnFinished += this.Voice_OnFinished; this._activeVoices.Add(voice); } diff --git a/Framework/Audio/Synthesizer/Voice.cs b/Framework/Audio/Synthesizer/Voice.cs index 12c37e3f..3e02f53f 100644 --- a/Framework/Audio/Synthesizer/Voice.cs +++ b/Framework/Audio/Synthesizer/Voice.cs @@ -6,7 +6,6 @@ /// A synthesizer voice. These are pooled and used to play notes to completion. /// public class Voice : IDisposable, IVoice { - private float _inverseSampleRate; private bool _isActive; private bool _isDisposed = false; private ulong _noteLengthInSamples; @@ -49,7 +48,7 @@ public AudioSample[] GetBuffer(ushort numberOfSamples) { if (this._isActive && sampleNumber >= 0) { var frequency = this.Note.GetFrequency((sampleNumber / (float)this._noteLengthInSamples).Clamp(0f, 1f)); var volume = this.GetSampleAmplitude((ulong)sampleNumber); - var time = sampleNumber * this._inverseSampleRate; + var time = sampleNumber * this._song.InverseSampleRate; var leftSample = this.Instrument.Oscillator.GetSignal(time, frequency, volume * this._track.LeftChannelVolume); var rightSample = this.Instrument.Oscillator.GetSignal(time, frequency, volume * this._track.RightChannelVolume); @@ -86,10 +85,8 @@ public virtual void Reinitialize(Song song, Track track, NoteInstance note, floa this._noteLengthInSamples = this._song.ConvertBeatsToSamples(this.Note.Length); this._preReleaseVolume = 0f; this._peakAmplitude = this.Envelope.Decay > 0 ? this.Envelope.PeakAmplitude : this.Envelope.SustainAmplitude; - this._inverseSampleRate = 1f / this._song.SampleRate; } - // To detect redundant calls protected virtual void Dispose(bool disposing) { if (!this._isDisposed) { this.OnFinished = null;