How to change the phase of a sine wave with PsychPortAudio in realtime?

I’m trying to create two channel audio comprising two discrete sine waves in MATLAB. The sine waves will be identical, except that I want the user to be able to change the phase of one of the sine waves. I posted a full description on Stack Overflow:

I realise it’s poor form to post on two forums. However, I’ve become increasingly concerned that MATLAB won’t be able to do what I want. For example, even with add-ons like the Digital Processing Toolbox and the Audio Toolbox, I worry about MATLAB’s sound handling and the possibility for unexpected behaviour. I thought PsychPortAudio might offer a better solution. If so, I will update the Stack Overflow thread.

For the user-controlled phase changes to a single sine wave, I thought the following might work in PsychPortAudio :

  1. Generate a single cycle of a sine wave using MATLAB routines (e.g. based on the sine function).
  2. Pass the single cycle sine wave data to PsychPortAudio in a format in which it can be output by PsychPortAudio.
  3. Have PsychPortAudio play back the single cycle sine wave infinitely, so that it will loop into a continuous tone.
  4. Enable user-controlled delays in the PsychPortAudio output. The delays should be fractions of a single cycle of a sine wave. For example, with a 500 Hz sine wave a single cycle is 2 milliseconds. So, the user-controlled delays would be between 0 and 2 milliseconds. These delays will be imperceptible to the user, but will have the effect of altering phase of the sine wave as desired.

Will this work? I’m not sure about steps (2) and (4). For (2), I wonder if the simplest solution is to write the sine wave data to disk (e.g. as a FLAC file) and have PsychPortAudio read it back in? For (4), I thought the Psychtoolbox features for frame buffering might be effective. However, these are generally described with application to screen draw. I wasn’t sure how they’d apply to buffering an audio loop.

I might misunderstand your purpose. You can simply make two sine wave with whatever phase difference you want, and play them as stereo. The better way may be to make it longer, rather than single sine wave. Then in case of the glitch between repeats, the artifact won’t be too frequent.

There’s a very elegant way to do it with PsychPortAudio, in realtime, allowing for continuous phase change on a sine-wave tone, without artifacts, under user control. It utilizes some math + PsychPortAudio’s high timing-precision realtime mixing abilities, audio slave devices and other goodies.

help PsychPaidSupportAndServices will tell you how to buy a community membership with priority support. With that, I can provide you with a demo that demonstrates this.

Best,
-mario

Hi xiangrui! I can of course prepare the sine waves in advance. However, I want the user to be able to change the phase of one of the sine waves whilst both sine waves are playing.

I’m going to deliver one of the sine waves over air conduction, and the other over bone conduction. When these two sine waves arrive at the inner ear 180 degrees out of phase, they will cancel each other out and the participant will hear nothing.

The phase settings differ slightly per participant (depends on head size, difference in propagation through air and the body, and so on). For this reason, it’s necessary for participants to be able to change the phase while the sine waves are already playing.

Hi Mario, I’d been hoping a solution based around PsychPortAudio would be possible.

I’m a student, and don’t have access to discretionary funds. Is there any kind of student rate available? Alternatively, could you point me towards the relevant documentation and perhaps I can figure it out for myself?

The idea is not that you buy it, but that your lab buys it. Something labs should do once a year independent of any specific support case anyway, given that one such license a year for a whole lab is no money for a normally funded lab at all, especially not in relationship to the value the software provides. Please talk to your supervisor about this.

I agree, but the lab I’m attached to won’t go for it. It’s the kind of thing I might be able to change when I’m running my own lab (and/or when I’m a staff member in a different lab). But for my PhD project I’m pretty much on my own. It’s a distinct project from what the rest of my department does :frowning:

So your lab doesn’t care about the quality of your - i assume tax payer funded - PhD research? Or about the - i assume paid - work time you spend figuring out a solution, so far going for over 16 days according to that StackOverflow thread? Or about contributing a bit to the upkeep of the tools they use for their research? But they do happily spend money for Matlab licenses and various toolbox licenses, e.g., the signal processing toolbox alone is around 200 Euros/year with academic discount, per installation? Or do you have to pay your computing equipment and Matlab licenses also yourself? I’d love to see the reasoning processes that go on in these sophisticated brains of lab leaders.

Yep, that’s pretty much it :weary:

I successfully bid for government funding for my research. This was competitive and difficult to achieve. It pays me a stipend (equivalent to a bit over minimum wage), and tuition fees which go directly to the university. The taught programme included a single lecture on MATLAB (really introductory – just how to use the interface) and there is a site licence consisting of about 200 installations across the entire university. These use a network key. So, if all 200 installations are in use when you try to open MATLAB, you won’t be able to and have to wait until someone closes MATLAB, freeing up a licence. There are no licences for any of the toolboxes, just the basic MATLAB package.

None of my supervisors do programming. The handful of people in the department who can program are always super-busy. The university will occasionally run introductory courses (e.g. for Python and so on) but these are expensive and non-specialist. This is at one of the bigger, more prestigious universities in my country. You’re pretty much left to your own devices, and have to solve research problems yourself. Academic staff aren’t happy about the situation, but there’s nothing much they can do. Funding for research is controlled by senior faculty, who focus on recruitment of overseas students. I feel worse for the overseas students than for me, because at least I understand the system and I don’t have high expectations of it.

I purchased the student MATLAB licence in 2018 (about $50, with a couple of the toolboxes) but the support from Mathworks only lasts a year. It at least gets around the restriction of network licences. I’d considered updating to the latest version and using the bundled Mathworks support. However, I’m concerned that MATLAB’s sound handling might be fiddly to set up (and/or unreliable) for what I want.

What I’ll probably do is to pay for the PsychPaidSupport myself, and try to recover the cost via my funder later on. There is a chance they’ll pay it back. But, the university won’t tell students how much money is available to them (I’m not making this up!). So, every time you ask for something, you have the worry that subsequent requests will be refused. I should add, this is not a procedure recommended by the government. It is how the university chooses to administer the government funds.

With the paid support, do I get to ask a few questions?

Because, the other thing I really need is sequences of tone bursts. These are just gated sine waves (ideally with a ramp up and down). The tone bursts should use the same phase and amplitude settings described in the the phase cancellation procedure that was my original question.

Can tone bursts also be made with PsychPortAudio? I’d guess so, but wanted to verify. For example, one way to do it could be to create a tone burst as a FLAC file, and then have PsychPortAudio control playback for the FLAC file.

Well, given i spent already more than the one hour that paid support pays for, thinking about a solution to your problem, technically it would be used up, but you get one bonus answer here:

Should work. There is BasicAMAndMixScheduleDemo.m which shows some of the mixing and amplitude modulation (AM) capabilities, cfe. variable pamodulator1. That can be used to define and apply such amplitude envelopes, e.g., ramp up/down, with well controlled sample-accurate timing. The demo uses variables envelope1, envelope2, envelope3 to define different envelopes for amplitude, sine-shaped in this case, ie. sine-wave sounds gated by sine-shaped AM envelopes.

The driver is very flexible, but ofc. only a subset of what is possible is exposed by the demos.
-mario

I’ve just purchased the community membership with priority support. Thanks for considering this request prior to receiving payment. I realise that the request exhausts the paid one hour support.

I can submit a formal request once the key activation clears (5-10 days). However, I thought I’d update the request here. It would also be great if the answer could be publicly available, so that other researchers have access to the solution. No particular rush for to respond – anything in the next week or two would be fine :slight_smile:

The basic request is for phase shifts as already described. I should emphasise that it is relative latency that is of interest here – that is, latency between the phases of two sine waves which are output on separate audio channels (e.g. the L/R channels of a standard sound card). Absolute latency (e.g. time between keyboard command and alteration to sound output over either channel) will of course be greater than zero. However, the experimental design is such that absolute latency is not a crucial factor. It is accurate measurement of the exact phase difference between the sine waves that is absolutely crucial. Sorry to go on about this, but I’ll be collecting a lot of data based on these settings and want to be as sure as possible that the sound output from the computer is as expected.

[I will, of course, carry out checks. These checks will compare outputs from transducers recorded using identical microphones and viewed on an oscilloscope. Thus, the checks will be entirely analogue, and will verify that the digital system (MATLAB and PsychPortAudio, plus hardware) is operating as expected.]

I had a look through the BasicAMAndMixScheduleDemo.m code. I found this surprisingly straightforward and readable in comparison to MATLAB code, and am optimistic about my capability to adapt it.

  • Presumably I should start by replacing MakeBeep (which uses the deprecated Snd command) with something based on the demo code you will provide for the phase change on sine waves?

  • I wondered about the use of WaitSecs(‘YieldSecs’, 0.1) (on lines 289 and 346). As I understand, this code interrupts the loop such that the status is queried every 0.1 seconds (100 milliseconds). This is no issue at all if the code is used solely to identify which of the 2.0 second duration tones is playing. However, there would be a serious issue if the code was instead used for timing accuracy, i.e. if the timing was only accurate to 100 milliseconds. I am keen to verify that the accuracy is better than 100 milliseconds!

I’ll be running the code on BSD/Linux. I’ve yet to install this (target machine is a 2016 Lenovo Thinkpad X1 Carbon 4th generation) so have some control over distribution. I can see that Ubuntu only gets official Lenovo support from the Thinkpad X1 Carbon 5th generation and onwards (a year after my machine). However, there are unofficial reports of Ubuntu working on the 4th generation. There also appears to be success with ArchLinux. Is it likely to make a difference which of Ubuntu or ArchLinux I install?

I just got an update of our database and it has one new key purchased 1st November. If you send me your authentication token, and that is the right one, we can continue here. You can also post your key directly if you want, as the key will be invalidated anyway, given the 1 hour is already exceeded.

That was me! Have just sent you an email confirming the token.

Didn’t receive anything? You can just post it here.

Oh! OK, it is FU89Y-D37Y9-TLA7Z-JLSER-TJNZ2-8RB7N

So the demo to look at is this new demo under the following link, which will be part of the next PTB release:

https://raw.githubusercontent.com/kleinerm/Psychtoolbox-3/master/Psychtoolbox/PsychDemos/BasicSoundPhaseShiftDemo.m

It should do what you want, and also demonstrate some visualization of the waveforms, as they are sent to the two output channels of the soundcard, either separate sine tone and then phase-shifted tone, or for debug/demo purposes also just a combined tone, which does the constructive/destructive interference mathematically before output. This is the signal as sent to the operating system, and before the system or sound hardware itself potentially messes with it, e.g., phase shifts etc. due to electronic band-pass filters and amplifiers in the sound cards analog circuitry. As you said, you will obviously have to check this with calibrated microphones and oscilloscope independent of the computer, but i thought it is a nice touch to also verify already before sending the signal to the outputs, and shows this output capture/loopback functionality of PsychPortAudio - not sure if it is demonstrated already in another demo or not.

As you can see, live changes of channel volumes on the audio output slave devices allow to change the relative amplitudes of the audio mix to create the phase-shifted cosine signal on the fly.

If you compare this to the BasicAMAndMixScheduleDemo you can see how to attach “modulator slave” devices to the audio output slave devices and then define AM modulation envelopes to apply some gating envelope, to generate burst tones or similar with precisely controlled timing.

You can also stop and start the audio playback of the slave devices independently, although that might not make much sense - it would introduce a phase-shift on its own.

Excellent choice of OS, and yes i assume that Thinkpad will work just fine. The Arch Wiki and other sources suggest so, and Lenovo has a generally good reputation wrt. Linux support. I tested this demo code myself under Linux, which should be a good choice to maximize the chance that what is specified in your script is actually what is output through the speakers.

Some versions of macOS on some hardware sometimes do stupid things like add their own attenuation or amplification / gain control to audio output signals, because Apple. So that’s one example of a risky choice of OS for reproducible audio playback.

I’d recommend Ubuntu 20.04.3-LTS for your purpose, simply because that is the generally recommended Linux distribution - the one i test with mostly. ArchLinux might work just as well, but PTB is not officially tested for compatibility or supported in case of problems - only recent Ubuntu is.

Btw. wrt. Matlab, it might be entirely possible that you can run your experiment simply with the free GNU/Octave which is part of Ubuntu, unless you’d need some special Matlab toolboxes that Octave doesn’t have in a compatible fashion. The demo script here was entirely developed and tested on Octave without any special toolboxes.

Best,
-mario

Found a little bug in my audio buffer size calculation, which triggers at 48kHz sample rate, as opposed to the previously tested 44.1kHz. See

Now tested to work on Linux/Windows/macOS at 44100 and 48000 Hz, the two most common rates. Get a new copy of the file, or copy and paste the 3 modified lines manually.

-mario

Thanks for this, Mario! The code is lovely. It is so much better than anything I could have written :slight_smile:

There are a few things I don’t understand, and I’ve also got some suggestions. I wondered if you could clarify, so I don’t make a mistake when making modifications.

Line 141: support = 2 * pi * freq * (0:round(wavedur * samplingRate-1)) / samplingRate;

I think the purpose of this is to create a vector containing the correct number of samples?

I wondered, why not use round(nsamples), rather than round(wavedur*samplingRate-1) ? Earlier, nsamples is defined as (wavedur*samplingRate), i.e. it’s near-identical to (wavedur*samplingRate-1). I can see that subtracting unity from the sampling rate is going to force a rounding up, and that doing so may increase accuracy with some sampling rates or wave durations. However, I’m looking also at the amplitude modulated instances. In those cases, wave durations can be quite long (includes a quiet time of maybe 0.25 secs between beeps) and there is a potential for error in the rounding up process. Scope for such an error would seem to be avoided by using round(nsamples). Is there any downside?

I also wondered why the support vector is scaled to (2 * pi * freq / samplingRate)? I couldn’t follow the reasoning for doing so. Later on, the support vector seems to be scaled again, by 0.5:

Line 160: PsychPortAudio('FillBuffer', pafixedsine, 0.5 * sin(support));

And then there is then a scaling of output volume, by 0.5:

Line 187: PsychPortAudio('Volume', pashiftsine, 0.5);

I’m trying to understand how these output levels are set because I want to be able to change the relative output levels of the two channels (i.e. left and right channels). I need to be able to do this to allow the user to fine tune relative outputs from the two different types of transducers (air conducted earphones, and bone conducted vibrator) which will be used. Presumably I can do so by altering the relative volumes of the pafixedsine and the pashiftsine channels? For example, I might use the same while loop which already performs a keyboard check for phase adjustment. So, I could set it such that left/right alters phase (as currently), and up/down alters relative volume.

I think a caveat of changing relative volume is that the sound levels may clip. This is why I want to be sure I understand what I’m changing. What I was planning is to set the up/down volume changes to increase the output of one channel and decrease the output of the other. With the right settings (i.e both channels within the -1.0 to +1.0 range of PsychPortAudio), I think it should be possible to alter relative levels such that one output channel is silent and the other is at (or near) the maximum output level it can have with no clipping?

There’s furthermore an issue of what the operating system might do, as per this warning on the PsychPortAudio-Volume guidance:

Caution: The volume settings only affect what is sent to your computers sound system. The operating system or audio hardware itself may apply additional volume settings, gains, filters etc., depending on your specific setup. These are only controllable via external system specific tools, PsychPortAudio doesn’t know about such additional settings and can’t control them in any way!

My intention was firstly to switch to Linux, and then to set the output volume to maximum using the operating system settings. This isn’t ideal (I’d prefer to leave a bit of headroom with the soundcard preamplifier) but it’s the only way I can think to ensure settings don’t change when I reboot the computer.

I’d really appreciate if you could advise on these issues :pray:. I’m currently about halfway through the modifications for the AM sequences, but wanted to verify that I’ve got the basics right before pressing on.

One other thing I should mention. When I’ve tested the code, I’ve tried outputting the phase reference into channel 2 (i.e. by setting targetChannel to 2), so that it mixes with the phase-shifted signal. When I do so, I hear both tones in one headphone and I can make the tones cancel (output level falls to zero) by changing the degree of phase-shift, as expected in the design of the code :slight_smile:.

What’s unusual is, whatever I set the original phase to be (i.e. changing phase setting on line 98, where the default is 90 degrees) I have to manually set the shifted phase to be 270 degrees to achieve cancellation. I’ve tried 0, 45 and 90 degrees, and even random original phase values such as 28 degrees. In every case the cancellation phase is reported as 270 degrees. I wondered if there was an issue with the phase change reporting not working as expected?