@pro-gram/flux 1.6.2 → 1.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@ namespace DrumBuddy
13
13
  {
14
14
  public string Name = "";
15
15
  public bool Mute, Solo;
16
- public double GainDb;
16
+ public double GainDb, Pan;
17
17
  public EditorTrack Copy() { return (EditorTrack)MemberwiseClone(); }
18
18
  }
19
19
 
@@ -34,20 +34,29 @@ namespace DrumBuddy
34
34
  public int Version = 1;
35
35
  public string Name = "Untitled session";
36
36
  public TempoGridOptions Grid = new TempoGridOptions();
37
+ public double MasterGainDb;
38
+ public bool MasterMuted;
37
39
  public EditorTrack[] Tracks = Enumerable.Range(1, 16).Select(i => new EditorTrack { Name = "Track " + i }).ToArray();
38
40
  public List<TimelineRegion> Regions = new List<TimelineRegion>();
39
41
  [XmlIgnore] public long End { get { return Regions.Count == 0 ? 0 : Regions.Max(r => r.End); } }
40
42
  public EditorSession Copy()
41
- { return new EditorSession { Version = Version, Name = Name, Grid = Grid.Copy(), Tracks = Tracks.Select(t => t.Copy()).ToArray(), Regions = Regions.Select(r => r.Copy()).ToList() }; }
43
+ { return new EditorSession { Version = Version, Name = Name, Grid = Grid.Copy(), MasterGainDb = MasterGainDb, MasterMuted = MasterMuted, Tracks = Tracks.Select(t => t.Copy()).ToArray(), Regions = Regions.Select(r => r.Copy()).ToList() }; }
44
+
45
+ public void ValidateMixer()
46
+ {
47
+ if (Tracks == null || Tracks.Length != 16 || double.IsNaN(MasterGainDb) || double.IsInfinity(MasterGainDb) || MasterGainDb < -60 || MasterGainDb > 12)
48
+ throw new IOException("The session has an invalid master level.");
49
+ foreach (var track in Tracks)
50
+ if (track == null || double.IsNaN(track.GainDb) || double.IsInfinity(track.GainDb) || track.GainDb < -60 || track.GainDb > 24 || double.IsNaN(track.Pan) || double.IsInfinity(track.Pan) || track.Pan < -1 || track.Pan > 1)
51
+ throw new IOException("The session has an invalid track level or pan position.");
52
+ }
42
53
 
43
54
  public void Validate()
44
55
  {
45
56
  if (Grid == null) Grid = new TempoGridOptions(); Grid.Validate();
46
57
  if (Version != 1 || Tracks == null || Tracks.Length != 16 || Regions == null || Regions.Count > 20000)
47
58
  throw new IOException("This is not a supported Flux 16-track editor session.");
48
- foreach (var track in Tracks)
49
- if (track == null || double.IsNaN(track.GainDb) || double.IsInfinity(track.GainDb) || track.GainDb < -60 || track.GainDb > 12)
50
- throw new IOException("The session has an invalid track level.");
59
+ ValidateMixer();
51
60
  foreach (var r in Regions)
52
61
  if (r == null || r.Track < 0 || r.Track >= 16 || string.IsNullOrWhiteSpace(r.Source) ||
53
62
  r.Start < 0 || r.SourceStart < 0 || r.Length <= 0 || r.Start > MaxFrame || r.SourceStart > MaxFrame || r.Length > MaxFrame || r.End > MaxFrame || r.FadeIn < 0 || r.FadeOut < 0 || r.FadeIn > MaxFrame || r.FadeOut > MaxFrame || r.FadeInOffset < 0 || r.FadeOutOffset < 0 || r.FadeInOffset > MaxFrame || r.FadeOutOffset > MaxFrame)
@@ -140,7 +149,7 @@ namespace DrumBuddy
140
149
  }
141
150
 
142
151
  // The edit model uses integer audio frames. Rendering does not modify source files.
143
- public sealed class TimelineMix : ISampleProvider, IDisposable
152
+ public sealed partial class TimelineMix : ISampleProvider, IDisposable
144
153
  {
145
154
  private sealed class Asset : IDisposable
146
155
  {
@@ -187,11 +196,11 @@ namespace DrumBuddy
187
196
  int frames = (int)Math.Min(count / 2, End - position);
188
197
  if (frames == 0) return 0;
189
198
  Array.Clear(output, offset, frames * 2);
199
+ PrepareTrackBuffers(frames * 2);
190
200
  bool solo = session.Tracks.Any(t => t.Solo);
191
201
  long blockEnd = position + frames;
192
202
  foreach (var r in session.Regions) {
193
- var track = session.Tracks[r.Track];
194
- if (r.Muted || track.Mute || (solo && !track.Solo) || r.Start >= blockEnd || r.End <= position) continue;
203
+ if (r.Muted || r.Start >= blockEnd || r.End <= position) continue;
195
204
  long from = Math.Max(r.Start, position), to = Math.Min(r.End, blockEnd);
196
205
  int take = (int)(to - from), skip = (int)(from - position);
197
206
  var asset = assets[r.Source]; int channels = asset.Reader.WaveFormat.Channels;
@@ -202,18 +211,35 @@ namespace DrumBuddy
202
211
  while (got < byteCount && (n = asset.Reader.Read(asset.Bytes, got, byteCount - got)) > 0) got += n;
203
212
  if (got != byteCount) throw new IOException("An audio source changed while playing. Reopen the session.");
204
213
  Buffer.BlockCopy(asset.Bytes, 0, asset.Samples, 0, got);
205
- float gain = (float)Math.Pow(10, track.GainDb / 20);
214
+ var trackBuffer = trackBuffers[r.Track];
206
215
  for (int f = 0; f < take; f++) {
207
216
  float envelope = (float)EditorTools.Envelope(r, from + f - r.Start);
208
- float left = asset.Samples[f * channels] * gain * envelope;
209
- float right = asset.Samples[f * channels + (channels == 1 ? 0 : 1)] * gain * envelope;
210
- output[offset + (skip + f) * 2] += left; output[offset + (skip + f) * 2 + 1] += right;
211
- TrackPeaks[r.Track] = Math.Max(TrackPeaks[r.Track], Math.Max(Math.Abs(left), Math.Abs(right)));
217
+ trackBuffer[(skip + f) * 2] += asset.Samples[f * channels] * envelope;
218
+ trackBuffer[(skip + f) * 2 + 1] += asset.Samples[f * channels + (channels == 1 ? 0 : 1)] * envelope;
219
+ }
220
+ }
221
+ for (int t = 0; t < 16; t++) {
222
+ var track = session.Tracks[t]; var trackBuffer = trackBuffers[t];
223
+ float gain = track.Mute || solo && !track.Solo ? 0 : (float)Math.Pow(10, track.GainDb / 20);
224
+ float leftGain = track.Pan >= 1 ? 0 : gain * (track.Pan > 0 ? (float)Math.Cos(track.Pan * Math.PI / 2) : 1);
225
+ float rightGain = track.Pan <= -1 ? 0 : gain * (track.Pan < 0 ? (float)Math.Cos(-track.Pan * Math.PI / 2) : 1);
226
+ for (int f = 0; f < frames; f++) {
227
+ float preLeft = trackBuffer[f * 2], preRight = trackBuffer[f * 2 + 1];
228
+ if (float.IsNaN(preLeft) || float.IsInfinity(preLeft) || float.IsNaN(preRight) || float.IsInfinity(preRight)) throw new IOException("The mix contains invalid audio samples.");
229
+ sourceMeterLeft[t] = Math.Max(sourceMeterLeft[t], Math.Abs(preLeft)); sourceMeterRight[t] = Math.Max(sourceMeterRight[t], Math.Abs(preRight));
230
+ float left = preLeft * leftGain, right = preRight * rightGain;
231
+ output[offset + f * 2] += left; output[offset + f * 2 + 1] += right;
232
+ outputMeterLeft[t] = Math.Max(outputMeterLeft[t], Math.Abs(left)); outputMeterRight[t] = Math.Max(outputMeterRight[t], Math.Abs(right));
233
+ TrackPeaks[t] = Math.Max(TrackPeaks[t], Math.Max(Math.Abs(left), Math.Abs(right)));
212
234
  }
213
235
  }
236
+ float masterGain = session.MasterMuted ? 0 : (float)Math.Pow(10, session.MasterGainDb / 20);
214
237
  for (int i = offset; i < offset + frames * 2; i++) {
238
+ output[i] *= masterGain;
215
239
  if (float.IsNaN(output[i]) || float.IsInfinity(output[i])) throw new IOException("The mix contains invalid audio samples.");
216
240
  Peak = Math.Max(Peak, Math.Abs(output[i]));
241
+ if ((i - offset) % 2 == 0) masterMeterLeft = Math.Max(masterMeterLeft, Math.Abs(output[i]));
242
+ else masterMeterRight = Math.Max(masterMeterRight, Math.Abs(output[i]));
217
243
  }
218
244
  position += frames; return frames * 2;
219
245
  }
@@ -0,0 +1,44 @@
1
+ using System;
2
+ using System.Linq;
3
+
4
+ namespace DrumBuddy
5
+ {
6
+ public sealed class TimelineMeterLevels
7
+ {
8
+ public readonly float[] SourceLeft = new float[16], SourceRight = new float[16], OutputLeft = new float[16], OutputRight = new float[16];
9
+ public float MasterLeft, MasterRight;
10
+ }
11
+ public sealed partial class TimelineMix
12
+ {
13
+ private readonly float[][] trackBuffers = Enumerable.Range(0, 16).Select(i => new float[0]).ToArray();
14
+ private readonly float[] sourceMeterLeft = new float[16], sourceMeterRight = new float[16], outputMeterLeft = new float[16], outputMeterRight = new float[16];
15
+ private float masterMeterLeft, masterMeterRight;
16
+ private void PrepareTrackBuffers(int samples)
17
+ {
18
+ for (int i = 0; i < 16; i++) {
19
+ if (trackBuffers[i].Length < samples) trackBuffers[i] = new float[samples];
20
+ else Array.Clear(trackBuffers[i], 0, samples);
21
+ }
22
+ }
23
+ public void UpdateMixer(EditorSession state)
24
+ {
25
+ state.ValidateMixer(); var tracks = state.Tracks.Select(t => t.Copy()).ToArray();
26
+ double master = state.MasterGainDb; bool muted = state.MasterMuted;
27
+ lock (gate) {
28
+ if (disposed) return;
29
+ session.Tracks = tracks; session.MasterGainDb = master; session.MasterMuted = muted;
30
+ }
31
+ }
32
+ public TimelineMeterLevels DrainMeters()
33
+ {
34
+ lock (gate) {
35
+ var result = new TimelineMeterLevels { MasterLeft = masterMeterLeft, MasterRight = masterMeterRight };
36
+ Array.Copy(sourceMeterLeft, result.SourceLeft, 16); Array.Copy(sourceMeterRight, result.SourceRight, 16);
37
+ Array.Copy(outputMeterLeft, result.OutputLeft, 16); Array.Copy(outputMeterRight, result.OutputRight, 16);
38
+ Array.Clear(sourceMeterLeft, 0, 16); Array.Clear(sourceMeterRight, 0, 16);
39
+ Array.Clear(outputMeterLeft, 0, 16); Array.Clear(outputMeterRight, 0, 16); masterMeterLeft = masterMeterRight = 0;
40
+ return result;
41
+ }
42
+ }
43
+ }
44
+ }