The signal’s period (in seconds) is the reciprocal of frequency. To get the period in frames, multiply this by the sample rate:
periodFrames = s.sampleRate / frequency
This is mostly correct. If the signal’s period is not an integer number of frames, it will drift somewhat because this number ultimately gets rounded (you can’t count a fractional number of frames).
~test = {SinOsc.ar(\freq.kr(108)) * 0.1}.play;
~left = Stethoscope(s, 1, 0);
// try to lock the waveform in place:
~left.cycle_(s.sampleRate / 108);
~left.cycle.postln; // not an integer -> drifting
// ...if you use integer periods, it locks:
~left.cycle_(196);
~test.set(\freq, s.sampleRate / 196);
And to be honest, I have no clue if hardwareBufferSize has any effect.