Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for new start/stop functionality of Ableton Link 3.0 #21

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions PdLinkSample/PdLinkSample/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11201" systemVersion="16A323" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11161"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
Expand All @@ -19,59 +22,74 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Tempo" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="58y-3V-cYd">
<frame key="frameInset" minX="20" minY="84" width="282" height="21"/>
<rect key="frame" x="20" y="84" width="282" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SDI-JT-j7F">
<frame key="frameInset" minX="20" minY="183" width="282" height="21"/>
<rect key="frame" x="20" y="183" width="282" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Quantum" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8ok-Bc-SDf">
<frame key="frameInset" minX="20" minY="289" width="282" height="21"/>
<rect key="frame" x="20" y="289" width="282" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<slider opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="120" minValue="20" maxValue="360" translatesAutoresizingMaskIntoConstraints="NO" id="eK0-65-Sz7">
<frame key="frameInset" minX="18" minY="113" width="286" height="31"/>
<rect key="frame" x="18" y="113" width="286" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<connections>
<action selector="tempoChanged:" destination="BYZ-38-t0r" eventType="valueChanged" id="rNK-WH-Scp"/>
</connections>
</slider>
<slider opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="1" maxValue="8" translatesAutoresizingMaskIntoConstraints="NO" id="YP3-Xy-SaD">
<frame key="frameInset" minX="18" minY="212" width="286" height="31"/>
<rect key="frame" x="18" y="212" width="286" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<connections>
<action selector="resolutionChanged:" destination="BYZ-38-t0r" eventType="valueChanged" id="cp1-to-dhS"/>
</connections>
</slider>
<slider opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="4" minValue="1" maxValue="8" translatesAutoresizingMaskIntoConstraints="NO" id="HNj-uK-g19">
<frame key="frameInset" minX="18" minY="310" width="286" height="31"/>
<rect key="frame" x="18" y="310" width="286" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<connections>
<action selector="quantumChanged:" destination="BYZ-38-t0r" eventType="valueChanged" id="h68-dE-Lgx"/>
</connections>
</slider>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="HY8-tG-OrM">
<frame key="frameInset" minX="20" minY="402" width="30" height="30"/>
<rect key="frame" x="20" y="437" width="30" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Link"/>
<connections>
<action selector="showLinkSettings:" destination="BYZ-38-t0r" eventType="touchUpInside" id="Jcv-cT-sgT"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Play" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qHt-5M-ndw">
<rect key="frame" x="20" y="385" width="32" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="UfC-bq-k7t">
<rect key="frame" x="74" y="385" width="49" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<connections>
<action selector="playStateChanged:" destination="BYZ-38-t0r" eventType="valueChanged" id="uOJ-Ut-8kG"/>
</connections>
</switch>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<connections>
<outlet property="playState" destination="UfC-bq-k7t" id="kws-Wf-ybG"/>
<outlet property="quantumLabel" destination="8ok-Bc-SDf" id="6H0-r6-8Ry"/>
<outlet property="quantumSlider" destination="HNj-uK-g19" id="Bht-TK-461"/>
<outlet property="resolutionLabel" destination="SDI-JT-j7F" id="yAY-9D-jhU"/>
Expand Down
2 changes: 2 additions & 0 deletions PdLinkSample/PdLinkSample/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ABLLinkStartStopSyncSupported</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
Expand Down
1 change: 1 addition & 0 deletions PdLinkSample/PdLinkSample/ViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
@property (weak, nonatomic) IBOutlet UISlider *tempoSlider;
@property (weak, nonatomic) IBOutlet UISlider *resolutionSlider;
@property (weak, nonatomic) IBOutlet UISlider *quantumSlider;
@property (weak, nonatomic) IBOutlet UISwitch *playState;

@end

22 changes: 20 additions & 2 deletions PdLinkSample/PdLinkSample/ViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

@interface ViewController ()
- (void)updateTempo:(int)tempo;
- (void)updatePlayState:(bool)is_playing;
@end

@implementation ViewController {
Expand All @@ -28,6 +29,11 @@ void sessionTempoCallback(double tempo, void *context) {
[vc updateTempo:tempo];
}

void playStateCallback(bool is_playing, void *context) {
ViewController *vc = (__bridge ViewController*) context;
[vc updatePlayState:is_playing];
}

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Expand All @@ -38,8 +44,10 @@ - (void)viewDidLoad {
ABLLinkRef linkRef = [appDelegate getLinkRef];
linkSettings_ = [ABLLinkSettingsViewController instance:linkRef];
ABLLinkSetSessionTempoCallback(linkRef, sessionTempoCallback, (__bridge void *)(self));
ABLLinkTimelineRef timeline = ABLLinkCaptureAppTimeline(linkRef);
[self updateTempo:ABLLinkGetTempo(timeline)];
ABLLinkSetStartStopCallback(linkRef, playStateCallback, (__bridge void *)(self));
ABLLinkSessionStateRef session_state = ABLLinkCaptureAppSessionState(linkRef);
[self updateTempo:ABLLinkGetTempo(session_state)];
[self updatePlayState:ABLLinkIsPlaying(session_state)];
}

- (void)didReceiveMemoryWarning {
Expand Down Expand Up @@ -68,6 +76,12 @@ - (IBAction)quantumChanged:(id)sender {
[PdBase sendFloat:quantum toReceiver:@"quantum"];
}

- (IBAction)playStateChanged:(id)sender {
UISwitch *sw = (UISwitch*) sender;
bool is_playing = sw.isOn;
[PdBase sendFloat:is_playing toReceiver:@"play"];
}

-(IBAction)showLinkSettings:(id)sender
{
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:linkSettings_];
Expand Down Expand Up @@ -102,5 +116,9 @@ - (void)updateTempo:(int)tempo {
self.tempoLabel.text = [NSString stringWithFormat:@"Tempo: %d", tempo];
}

- (void)updatePlayState:(bool)is_playing {
[self.playState setOn:is_playing];
}

@end

11 changes: 9 additions & 2 deletions PdLinkSample/PdLinkSample/ping.pd
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#X obj 250 219 print beat;
#X msg 56 64 tempo \$1;
#X msg 119 64 resolution \$1;
#X msg 216 64 reset 0 \$1;
#X msg 216 65 reset 0 \$1;
#X floatatom 56 45 5 0 0 2 tempo tempo -, f 5;
#X floatatom 119 46 5 0 0 2 resolution resolution -, f 5;
#X floatatom 216 46 5 0 0 2 quantum quantum -, f 5;
Expand All @@ -25,7 +25,11 @@
#X obj 148 233 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144
-1 -1;
#X obj 112 258 print downbeat;
#X obj 234 134 print tempo;
#X obj 248 156 print tempo;
#X obj 251 134 print play;
#X obj 290 45 tgl 15 0 empty play play 17 7 0 10 -262144 -1 -1 0 1
;
#X msg 290 65 play \$1;
#X connect 0 0 1 1;
#X connect 1 0 8 0;
#X connect 3 0 1 0;
Expand All @@ -46,6 +50,7 @@
#X connect 17 1 6 1;
#X connect 17 2 5 1;
#X connect 17 3 24 0;
#X connect 17 4 25 0;
#X connect 18 0 0 0;
#X connect 19 0 4 0;
#X connect 19 1 20 0;
Expand All @@ -56,3 +61,5 @@
#X connect 21 0 18 0;
#X connect 21 0 23 0;
#X connect 22 0 16 0;
#X connect 26 0 27 0;
#X connect 27 0 17 0;
2 changes: 1 addition & 1 deletion abl_link/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The version of abl_link~ for desktop and Android has moved to https://github.com

## iOS version

**This version uses Ableton LinkKit 2.0.0.**
**This version uses Ableton LinkKit 3.0.**

To see the iOS version of abl_link~ in action, try PdLinkSample in this repository.

Expand Down
6 changes: 3 additions & 3 deletions abl_link/ios/PdLinkAudioUnit.m
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ static OSStatus AudioRenderCallback(void *inRefCon,
AudioUnitRender([pdAudioUnit audioUnit], ioActionFlags, inTimeStamp, kInputElement, inNumberFrames, ioData);
}

ABLLinkTimelineRef timeline = ABLLinkCaptureAudioTimeline(pdAudioUnit->linkRef_);
abl_link_set_timeline(timeline);
ABLLinkSessionStateRef session_state = ABLLinkCaptureAudioSessionState(pdAudioUnit->linkRef_);
abl_link_set_session_state(session_state);
int ticks = inNumberFrames / kPdBlockSize;
UInt64 hostTimeAfterTick = inTimeStamp->mHostTime + pdAudioUnit->outputLatency_;
int bufSizePerTick = kPdBlockSize * pdAudioUnit->numChannels_;
Expand All @@ -98,7 +98,7 @@ static OSStatus AudioRenderCallback(void *inRefCon,
[PdBase processFloatWithInputBuffer:auBuffer outputBuffer:auBuffer ticks:1];
auBuffer += bufSizePerTick;
}
ABLLinkCommitAudioTimeline(pdAudioUnit->linkRef_, timeline);
ABLLinkCommitAudioSessionState(pdAudioUnit->linkRef_, session_state);

return noErr;
}
Expand Down
34 changes: 26 additions & 8 deletions abl_link/ios/abl_link.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
#include <string.h>

static ABLLinkRef libpdLinkRef = NULL;
static ABLLinkTimelineRef libpd_timeline;
static ABLLinkSessionStateRef libpd_session_state;
static uint64_t libpd_curr_time;

void abl_link_set_link_ref(ABLLinkRef ref) {
libpdLinkRef = ref;
}

void abl_link_set_timeline(ABLLinkTimelineRef timeline) {
libpd_timeline = timeline;
void abl_link_set_session_state(ABLLinkSessionStateRef session_state) {
libpd_session_state = session_state;
}

void abl_link_set_time(uint64_t curr_time) {
Expand All @@ -36,10 +36,12 @@ typedef struct _abl_link_tilde {
t_outlet *phase_out;
t_outlet *beat_out;
t_outlet *tempo_out;
t_outlet *is_playing_out;
double steps_per_beat;
double prev_beat_time;
double quantum;
double tempo;
int is_playing;
int reset_flag;
} t_abl_link_tilde;

Expand All @@ -54,22 +56,30 @@ static void abl_link_tilde_dsp(t_abl_link_tilde *x, t_signal **sp) {
}

static void abl_link_tilde_tick(t_abl_link_tilde *x) {
if (x->is_playing < 0) {
ABLLinkSetIsPlaying(libpd_session_state, x->is_playing + 2, libpd_curr_time);
}
const int prev_play = x->is_playing;
x->is_playing = ABLLinkIsPlaying(libpd_session_state);
if (prev_play != x->is_playing) {
outlet_float(x->is_playing_out, x->is_playing);
}
if (x->tempo < 0) {
ABLLinkSetTempo(libpd_timeline, -x->tempo, libpd_curr_time);
ABLLinkSetTempo(libpd_session_state, -x->tempo, libpd_curr_time);
}
double prev_tempo = x->tempo;
x->tempo = ABLLinkGetTempo(libpd_timeline);
x->tempo = ABLLinkGetTempo(libpd_session_state);
if (prev_tempo != x->tempo) {
outlet_float(x->tempo_out, x->tempo);
}
double curr_beat_time;
if (x->reset_flag) {
ABLLinkRequestBeatAtTime(libpd_timeline, x->prev_beat_time, libpd_curr_time, x->quantum);
curr_beat_time = ABLLinkBeatAtTime(libpd_timeline, libpd_curr_time, x->quantum);
ABLLinkRequestBeatAtTime(libpd_session_state, x->prev_beat_time, libpd_curr_time, x->quantum);
curr_beat_time = ABLLinkBeatAtTime(libpd_session_state, libpd_curr_time, x->quantum);
x->prev_beat_time = curr_beat_time - 1e-6;
x->reset_flag = 0;
} else {
curr_beat_time = ABLLinkBeatAtTime(libpd_timeline, libpd_curr_time, x->quantum);
curr_beat_time = ABLLinkBeatAtTime(libpd_session_state, libpd_curr_time, x->quantum);
}
outlet_float(x->beat_out, curr_beat_time);
double curr_phase = fmod(curr_beat_time, x->quantum);
Expand All @@ -94,6 +104,10 @@ static void abl_link_tilde_set_tempo(t_abl_link_tilde *x, t_floatarg bpm) {
x->tempo = -bpm; // Negative values signal tempo changes.
}

static void abl_link_tilde_play(t_abl_link_tilde *x, t_floatarg is_playing) {
x->is_playing = (is_playing != 0) - 2;
}

static void abl_link_tilde_set_resolution(t_abl_link_tilde *x, t_floatarg steps_per_beat) {
x->steps_per_beat = steps_per_beat;
}
Expand All @@ -120,10 +134,12 @@ static void *abl_link_tilde_new(t_symbol *s, int argc, t_atom *argv) {
x->phase_out = outlet_new(&x->obj, &s_float);
x->beat_out = outlet_new(&x->obj, &s_float);
x->tempo_out = outlet_new(&x->obj, &s_float);
x->is_playing_out = outlet_new(&x->obj, &s_float);
x->steps_per_beat = 1;
x->prev_beat_time = 0;
x->quantum = 4;
x->tempo = 0;
x->is_playing = 2;
x->reset_flag = 1;
switch (argc) {
default:
Expand Down Expand Up @@ -159,6 +175,8 @@ void abl_link_tilde_setup(void) {
gensym("dsp"), 0);
class_addmethod(abl_link_tilde_class, (t_method)abl_link_tilde_enable,
gensym("connect"), A_DEFFLOAT, 0);
class_addmethod(abl_link_tilde_class, (t_method)abl_link_tilde_play,
gensym("play"), A_DEFFLOAT, 0);
class_addmethod(abl_link_tilde_class, (t_method)abl_link_tilde_set_tempo,
gensym("tempo"), A_DEFFLOAT, 0);
class_addmethod(abl_link_tilde_class, (t_method)abl_link_tilde_set_resolution,
Expand Down