NAudio: How to accurately get the current play location when changing play position using AudioFileReader and WaveOutEvent - naudio

I'm creating an application that needs to allow the user to select the play position of an audio file while the file is playing. However, once the position is changed, I'm having trouble identifying the current position.
Here's an example program that shows how I'm going about it.
using NAudio.Utils;
using NAudio.Wave;
using System;
namespace NAudioTest
{
class Program
{
static void Main()
{
var audioFileReader = new AudioFileReader("test.mp3");
var waveOutEvent = new WaveOutEvent();
waveOutEvent.Init(audioFileReader);
waveOutEvent.Play();
while (true)
{
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.Enter)
{
var playLocationSeconds =
waveOutEvent.GetPositionTimeSpan().TotalSeconds;
Console.WriteLine(
"Play location is " + playLocationSeconds + "seconds");
}
else if (key.Key == ConsoleKey.RightArrow)
{
audioFileReader.CurrentTime =
audioFileReader.CurrentTime.Add(TimeSpan.FromSeconds(30));
}
else
{
break;
}
}
}
}
}
Steps to reproduce the problem
Start the program: the audio file starts playing
Press the enter key: the current play time is written to the console
Press the right arrow key: the played audio jumps ahead (presumably) to the expected location
Press the Enter key again: a play time is written to the
console, but it looks to be the amount of time since the audio first
started playing, not the time of the current play position.
I have tried getting the value of AudioFileReader.CurrentTime instead of calling GetPositionTimeSpan on the WaveOutEvent. The problem with this approach is that the AudioFileReader.CurrentTime value proceeds in jumps, presumably because this underlying stream is buffered when used with WaveOutEvent so CurrentTime does not accurately reflect the play position, only the position in the underlying stream.
How do I support arbitrary play positioning yet continue to get an accurate play position current time?

The "CurrentTime" property of your audio file reader is good enough to tell the current position of playback, especially if your latency is not very high. I found the difference between it and waveOutEvent.GetPositionTimeSpan() to be 100-200 ms. at most.
You are indeed using the setter of the CurrentTime property to reposition within the stream. It would be consistent to use the getter to then query the current position as well. If you are concerned with precision, you can use lower latency.
The extension method "GetPositionTimeSpan()" does seem to return the total length of playback so far and not the position within the stream. Admittedly I do not know why this is so.

Related

How to mute/unmute mic in webrtc

I have read from here that how i can mute/unmute mic for a localstream in webrtc:WebRTC Tips & Tricks
When i start my localstream mic is enable at that time by default so when i set audioTracks[0].enabled=false it muted a mic in my local stream but when i set it back true it enable to unmute. Here is my code mute/unmute for a localstream:
getLocalStream(function (stream,enable) {
if (stream) {
for (var i = 0; i < stream.getTracks().length; i++) {
var track = stream.getAudioTracks()[0];
if (track)
track.enabled = enable;
//track.stop();
}
}
});
Can someone suggest me how i can unmute mic back in a localstream.
I assume that your method getLocalStream is actually calling navigator.getUserMedia. In this case when you do this you'll get another stream, not the original one. Using the orignal stream you should do
mediaStream.getAudioTracks()[0].enabled = true; // or false to mute it.
Alternatively you can check https://stackoverflow.com/a/35363284/1210071
There are 2 properties enabled and muted.
enabled is for setting, and muted is read-only on the remote side (the other person) (I have tried, setting muted does not work, basically, value cannot be changed)
stream.getAudioTracks()[0].enabled = true; // remote one will get muted change
Ahhh there is a good way to do this:
mediaStream.getVideoTracks()[0].enabled = !(mediaStream.getVideoTracks()[0].enabled);
You should read and set the "enabled" value. The "enabled" value is for 'muting'. The "muted" value is a read-only value to do with whether the stream is currently unable to play.
The enabled property on the MediaStreamTrack interface is a Boolean value which is true if the track is allowed to render the source stream or false if it is not. This can be used to intentionally mute a track. When enabled, a track's data is output from the source to the destination; otherwise, empty frames are output.
In the case of audio, a disabled track generates frames of silence (that is, frames in which every sample's value is 0). For video tracks, every frame is filled entirely with black pixels.
The value of enabled, in essence, represents what a typical user would consider the muting state for a track, whereas the muted property indicates a state in which the track is temporarily unable to output data, such as a scenario in which frames have been lost in transit.
https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/enabled
Step 1) call jquery.min.js
Step 2) use below code ,
A) To Mute
$("video").prop('muted','true');
B) To unmute
$("video").prop('muted','');
single Icon mute and unmute like youtube
function enablemute(thisimag) {
if($(thisimag).attr('src')=='images/mute.png')
{
$("video").prop('muted','');
$(thisimag).prop('src','images/unmute.png');
}
else
{
//alert('her');
$("video").prop('muted','true');
$(thisimag).prop('src','images/mute.png');
}
}
above function enablemute should call from onclick

Can't fade out with FadeInOutSampleProvider

I'm trying to use NAudio's FadeInOutSampleProvider to fade in a sample and fade it out. The fade in works OK, but instead of fading out gradually I get abrupt silence from where the fade-out should begin.
What's the correct way to fade out with FadeInOutSampleProvider?
Here's how I'm trying to do it:
IWaveProvider waveSource; // initialised by reading a WAV file
// The ISampleProvider will be the underlying source for the following operations
ISampleProvider sampleSource = waveSource.ToSampleProvider();
// Create a provider which defines the samples we want to fade in
// (including the full-volume "middle" of the final output)
ISampleProvider fadeInSource = new OffsetSampleProvider(sampleSource);
fadeInSource.TakeSamples = most_of_file; // calculation omitted for brevity
// Create a provider which defines the samples we want to fade out:
// We will play these samples when fadeInSource is finished
ISampleProvider fadeOutSource = new OffsetSampleProvider(sampleSource);
fadeOutSource.SkipOverSamples = fadeInSource.TakeSamples;
// Wrap the truncated sources in FadeInOutSampleProviders
var fadeIn = new FadeInOutSampleProvider(fadeInSource);
fadeIn.BeginFadeIn(500); // half-second fade
var fadeOut = new FadeInOutSampleProvider(fadeOutSource);
fadeOut.BeginFadeOut(500);
// doc-comments suggest the fade-out will begin "after first Read"
I'm expecting fadeOut to initially read non-zero samples from 500ms before the end of the original source, but fade out to zeros by the end of the source.
However, when I play fadeIn to completion, then play fadeOut, I find that the very first Read call to fadeOut fills the buffer with zeros.
Am I doing something wrong? Or is there a bug in NAudio?
Note: I'm handling the sequential playback using a ConcatenatingSampleProvider which I implemented myself — I can't anything similar in NAudio's API. It's pretty trivial, so I've omitted the source here.
The problem is you're trying to reuse sampleSource twice in your graph. So sampleSource has already been read to the end before anything is read from fadeOutSource. Probably for your usage, it would be better for FadeInOutSampleProvider to be able to "schedule" a fade-out after a known number of samples.
An alternative approach is a FadeOutSampleProvider that caches the fade-out duration, and then when it detects the end of its source has been reached, it returns the cached portion faded out. It does mean latency is introduced.

How to step one frame forward and one frame backward in video playback?

I need to search for some special features/patterns that might be visible in only one (or two) of many frames. The frame rate can be as slow as 25 frames per second and the video may contain over 7500 frames. I often start by scanning the video at high speed looking for a segment where I might find the feature, then rewind. I repeat this procedure while gradually reducing the playback speed, until I find a fairly small time window in which I can expect to find the feature (if it is present). I would then like to step forward and backward by single frames using key hit events (e.g. right arrow and left arrow keys) to find the feature of interest. I have managed to use HTML5 with JavaScript to control the forward speed; but, still do not know how to use the keyboard for single frame stepping forward and backward through a video. How can this be accomplished? Note, my web browser is Firefox 26 running on a Windows 7 platform.
You can seek to any time in the video by setting the currentTime property. Something like this:
var video = document.getElementById('video'),
frameTime = 1 / 25; //assume 25 fps
window.addEventListener('keypress', function (evt) {
if (video.paused) { //or you can force it to pause here
if (evt.keyCode === 37) { //left arrow
//one frame back
video.currentTime = Math.max(0, video.currentTime - frameTime);
} else if (evt.keyCode === 39) { //right arrow
//one frame forward
//Don't go past the end, otherwise you may get an error
video.currentTime = Math.min(video.duration, video.currentTime + frameTime);
}
}
});
Just a couple things you need to be aware of, though they shouldn't cause you too much trouble:
There is no way to detect the frame rate, so you have to either hard-code it or list it in some lookup table or guess.
Seeking may take a few milliseconds or more and does not happen synchronously. The browser needs some time to load the video from the network (if it's not already loaded) and to decode the frame. If you need to know when seeking is done, you can listen for the 'seeked' event.
You can check to see if the video has advanced to the next frame by checking the
targetTime += (1 / 25) // 25 fps
video.currentTime = targetTime // set the video frame
if (video.currentTime >= video.duration) { // if it's the end of the video
alert('done!');
}
if (video.currentTime == targetTime) { // frame has been updated
}

Using a simulated event BecomeViewTarget to make an isometric camera

I'm following a tutorial on making a top down/isometric camera and have run into a bit of a snag. See, the following comes up when I compile.
BGCGamePawn.uc(15) : Error, Type mismatch in '='
Now, I've managed to get this far so I understand that the problem lies in the following bit of code. Line 15 is bold.
//override to make player mesh visible by default
simulated event BecomeViewTarget( PlayerController PC )
{
local UTPlayerController UTPC;
Super.BecomeViewTarget(PC);
if (LocalPlayer(PC.Player) != None)
{
**UTPC = BGCGamePlayerController (PC);**
if (UTPC != None)
{
//set player ctrl to behind view and make mesh visible
UTPC.SetBehindView(true);
SetMeshVisibility(True);
UTPC.bNoCrosshair = true;
}
}
}
Does BGCGamePlayerController extend from UTPlayerController? If not, that would be the problem: you're trying to cast your PlayerController parameter into a BGCGamePlayerController but then store it in a local UTPlayerController variable. You'll need to change the type for your local variable or change the hierarchy for your BGCGamePlayerController.

Web audio API polyphony - using 2 different gain nodes not working?

I can't seem to create two oscillators with independent gain envelopes.
The code below creates two buttons which each play a sine tone at a different pitch. When I click on the first button, I hear the tone grow in volume as it should. But, when I click the second button, the tone reacts as if it is connected to the gain of the first tone. For example, if I click the second button (turning on the second tone) while the first tone is at volume 1, the second tone will enter at volume 1, even though it is supposed to envelope from 0 to 1 to 0 over the course of 10 seconds.
Can I only have one gain node per audio context? Or is there some other reason that the gains of these oscillators are being connected? In addition, after I play the tones once, I cannot play them again, which makes me especially think that I am doing something wrong. : )
Thanks. A link is below and the code is below that. This is my first post here so let me know if you need anything else. This code must be run in versions of Chrome or Safari that support the web audio api.
http://whitechord.org/just_mod/poly_test.html
WAAPI tests
<button onclick="play()">play one</button>
<button onclick="play2()">play two</button>
<script>
var context;
window.addEventListener('load', initAudio, false);
function initAudio() {
try {
context = new webkitAudioContext();
} catch(e) {
onError(e);
}
}
function play() {
var oscillator = context.createOscillator();
var gainNode = context.createGainNode();
gainNode.gain.value = 0.0;
oscillator.connect(gainNode);
gainNode.connect(context.destination);
oscillator.frequency.value = 700;
gainNode.gain.linearRampToValueAtTime(0.0, 0); // envelope
gainNode.gain.linearRampToValueAtTime(0.1, 5); // envelope
gainNode.gain.linearRampToValueAtTime(0.0, 10); // envelope
oscillator.noteOn(0);
}
function play2() {
var oscillator2 = context.createOscillator();
var gainNode2 = context.createGainNode();
gainNode2.gain.value = 0.0;
oscillator2.connect(gainNode2);
gainNode2.connect(context.destination);
oscillator2.frequency.value = 400;
gainNode2.gain.linearRampToValueAtTime(0.0, 0); // envelope
gainNode2.gain.linearRampToValueAtTime(0.1, 5); // envelope
gainNode2.gain.linearRampToValueAtTime(0.0, 10); // envelope
oscillator2.noteOn(0);
}
/* error */
function onError(e) {
alert(e);
}
</script>
</body>
</html>
Can I only have one gain node per audio context? Or is there some other reason that the gains of these oscillators are being connected? In addition, after I play the tones once, I cannot play them again, which makes me especially think that I am doing something wrong. : )
You can have as many gain nodes as you want (this is how you could achieve mixing bus-like setups, for example), so that's not the problem. Your problem is the following:
Remember that the second parameter to linearRampToValueAtTime() is time in the same time coordinate system as your context.currentTime.
And your context.currentTime is always moving forward in real time, so all your ramps, curves, etc. should be calculated relative to it.
If you want something to happen 4 seconds from now, you'd pass context.currentTime + 4 to the Web Audio API function.
So, change all your calls linearRampToValueAtTime() in your code, so that they look like:
gainNode2.gain.linearRampToValueAtTime(0.0, context.currentTime); // envelope
gainNode2.gain.linearRampToValueAtTime(0.1, context.currentTime + 5); // envelope
gainNode2.gain.linearRampToValueAtTime(0.0, context.currentTime + 10); // envelope
And that should take care of your issues.
BTW you have a stray double quote in your BODY opening markup tag.
Ask Will Conklin
gainNode2.gain.linearRampToValueAtTime(0.1, context.currentTime + 5); // envelope