Skip to content

Commit ff12125

Browse files
authored
Fix rounding errors (#22)
- Convert time -> sample time instead of sample time -> time - This will be more precise as we are not working with fractions - Use double instead of float in APIs (AudioTimeStamp->mSampleTime is double)
1 parent 5e8d368 commit ff12125

File tree

4 files changed

+35
-20
lines changed

4 files changed

+35
-20
lines changed

Sources/AudioKitEX/Automation/NodeParameter+Automation.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ extension NodeParameter {
4747

4848
guard let observer = ParameterAutomationGetRenderObserver(parameter.address,
4949
avAudioNode.auAudioUnit.scheduleParameterBlock,
50-
Float(Settings.sampleRate),
51-
Float(lastTime.sampleTime),
50+
Double(Settings.sampleRate),
51+
Double(lastTime.sampleTime),
5252
automationBaseAddress,
5353
events.count) else { return }
5454

Sources/CAudioKitEX/Sequencing/ParameterAutomation.mm

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
extern "C"
1313
AURenderObserver ParameterAutomationGetRenderObserver(AUParameterAddress address,
1414
AUScheduleParameterBlock scheduleParameterBlock,
15-
float sampleRate,
16-
float startSampleTime,
15+
double sampleRate,
16+
double startSampleTime,
1717
const struct AutomationEvent* eventsArray,
1818
size_t count) {
1919

@@ -33,16 +33,18 @@ AURenderObserver ParameterAutomationGetRenderObserver(AUParameterAddress address
3333
{
3434
if (actionFlags != kAudioUnitRenderAction_PreRender) return;
3535

36-
float blockStartTime = (timestamp->mSampleTime - startSampleTime) / sampleRate;
37-
float blockEndTime = blockStartTime + frameCount / sampleRate;
36+
double blockStartSample = timestamp->mSampleTime - startSampleTime;
37+
double blockEndSample = blockStartSample + frameCount;
3838

3939
AUValue initial = NAN;
4040

4141
// Skip over events completely in the past to determine
4242
// an initial value.
4343
for (; index < count; ++index) {
4444
auto event = events[index];
45-
if ( !(event.startTime + event.rampDuration < blockStartTime) ) {
45+
double eventStartSample = event.startTime * sampleRate;
46+
double rampSampleDuration = event.rampDuration * sampleRate;
47+
if ( !(eventStartSample + rampSampleDuration < blockStartSample) ) {
4648
break;
4749
}
4850
initial = event.targetValue;
@@ -59,11 +61,12 @@ AURenderObserver ParameterAutomationGetRenderObserver(AUParameterAddress address
5961
// Apply parameter automation for the segment.
6062
while (index < count) {
6163
auto event = events[index];
64+
double eventStartSample = event.startTime * sampleRate;
6265

6366
// Is it after the current block?
64-
if (event.startTime >= blockEndTime) break;
67+
if (eventStartSample >= blockEndSample) break;
6568

66-
AUEventSampleTime startTime = (event.startTime - blockStartTime) * sampleRate;
69+
AUEventSampleTime startTime = eventStartSample - blockStartSample;
6770
AUAudioFrameCount duration = event.rampDuration * sampleRate;
6871

6972
// If the event has already started, ensure we hit the targetValue

Sources/CAudioKitEX/include/ParameterAutomation.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ struct AutomationEvent {
2626
/// Returns a render observer block which will apply the automation to the selected parameter.
2727
AURenderObserver ParameterAutomationGetRenderObserver(AUParameterAddress address,
2828
AUScheduleParameterBlock scheduleParameterBlock,
29-
float sampleRate,
30-
float startSampleTime,
29+
double sampleRate,
30+
double startSampleTime,
3131
const struct AutomationEvent* events,
3232
size_t count);
3333

Tests/AudioKitEXTests/ParameterAutomationTests.swift

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ class ParameterAutomationTests: XCTestCase {
99

1010
func observerTest(events: [AutomationEvent],
1111
sampleTime: Float64,
12-
startTime: Float = 0) -> ([AUParameterAddress], [AUValue], [AUAudioFrameCount]) {
12+
startTime: Double = 0) -> ([AUParameterAddress], [(AUValue, AUEventSampleTime)], [AUAudioFrameCount]) {
1313

1414
let address = AUParameterAddress(42)
1515

1616
var addresses: [AUParameterAddress] = []
17-
var values: [AUValue] = []
17+
var values: [(AUValue, AUEventSampleTime)] = []
1818
var durations: [AUAudioFrameCount] = []
1919

2020
let scheduleParameterBlock: AUScheduleParameterBlock = { (sampleTime, rampDuration, address, value) in
2121
addresses.append(address)
22-
values.append(value)
22+
values.append((value, sampleTime))
2323
durations.append(rampDuration)
2424
}
2525

@@ -42,13 +42,25 @@ class ParameterAutomationTests: XCTestCase {
4242

4343
func testSimpleAutomation() throws {
4444

45-
let events = [ AutomationEvent(targetValue: 880, startTime: 0, rampDuration: 1.0) ]
45+
let events = [AutomationEvent(targetValue: 880, startTime: 0, rampDuration: 0)]
4646

4747
let (addresses, values, _) = observerTest(events: events, sampleTime: 0)
4848

4949
// order is: taper, skew, offset, value
5050
XCTAssertEqual(addresses, [42])
51-
XCTAssertEqual(values, [880.0])
51+
XCTAssertEqual(values.map(\.0), [880.0])
52+
}
53+
54+
func testSimpleAutomationWithBigValues() throws {
55+
56+
let events = [AutomationEvent(targetValue: 880, startTime: 0, rampDuration: 0)]
57+
58+
let (addresses, values, _) = observerTest(events: events, sampleTime: 1000000000, startTime: 1000000001)
59+
60+
// order is: taper, skew, offset, value
61+
XCTAssertEqual(addresses, [42])
62+
XCTAssertEqual(values.map(\.0), [880.0])
63+
XCTAssertEqual(values.map(\.1), [1])
5264
}
5365

5466
func testPastAutomation() {
@@ -59,7 +71,7 @@ class ParameterAutomationTests: XCTestCase {
5971

6072
// If the automation is in the past, the value should be set to the final value.
6173
XCTAssertEqual(addresses, [42])
62-
XCTAssertEqual(values, [880.0])
74+
XCTAssertEqual(values.map(\.0), [880.0])
6375
}
6476

6577
func testPastAutomationTwo() {
@@ -71,7 +83,7 @@ class ParameterAutomationTests: XCTestCase {
7183

7284
// If the automation is in the past, the value should be set to the final value.
7385
XCTAssertEqual(addresses, [42])
74-
XCTAssertEqual(values, [440.0])
86+
XCTAssertEqual(values.map(\.0), [440.0])
7587

7688
}
7789

@@ -83,7 +95,7 @@ class ParameterAutomationTests: XCTestCase {
8395

8496
// If the automation is in the future, we should not get anything.
8597
XCTAssertEqual(addresses, [])
86-
XCTAssertEqual(values, [])
98+
XCTAssertEqual(values.map(\.0), [])
8799
}
88100

89101
func testAutomationMiddle() {
@@ -95,7 +107,7 @@ class ParameterAutomationTests: XCTestCase {
95107
let (addresses, values, durations) = observerTest(events: events, sampleTime: 128)
96108

97109
XCTAssertEqual(addresses, [42])
98-
XCTAssertEqual(values, [1.0])
110+
XCTAssertEqual(values.map(\.0), [1.0])
99111
XCTAssertEqual(durations, [UInt32(44100-128)])
100112
}
101113

0 commit comments

Comments
 (0)