Has anyone been able to loop a MIDI file without problems on IOS9 Beta?
As soon as I try to loop by setting numberOfLoops to 0 in MusicTrackLoopInfo, it locks up the app by sending random MIDI to the player. I've reported it, but am wondering if anyone has found a work around. The same code works perfectly under all other iOS versions.
MusicTrackLoopInfo loopInfo;
loopInfo.loopDuration = loopLength;
loopInfo.numberOfLoops = 0;
OK I just heard iOS9 will ship with this bug in it. Terrible.
Here is a work around.
Don't set numberOfLoops at all, OR set numberOfLoops = 1; // means loop once
Now make a variable (i.e. myVariableToKeepTrackOfAddedCopies) that keeps track of the number of times you will actually perform the following:
In your MIDIReadProc at some point BEFORE the track has finished playing, do the following:
// Copy the track to itself - effectively doubling the length
MusicTrack theTrack=nil;
MusicTrackGetProperty(theTrack, kSequenceTrackProperty_TrackLength, &trackLen, &trackLenLen);
trackLen = 4.0; //<-- this is your real track length
MusicTrackCopyInsert(theTrack, 0, trackLen, theTrack, 0);
myVariableToKeepTrackOfAddedCopies++;
So now your track is twice as long before it ends and the track will continue. This will work the same as looping except you are taking up more memory since you are making the track length longer after each iteration.
When you stop the sequence/track, cut the track back to the original size.
MusicTrackCut(theTrack, 4.0, 4.0 + (4.0*myVariableToKeepTrackOfAddedCopies));
MusicTrackGetProperty(theTrack, kSequenceTrackProperty_TrackLength, &trackLen, &trackLenLen);
Irritating, but it works. I just verified on iOS9 beta 5. Hope it helps.
This is fixed as of iOS release 9.2
Oddly enough, the tempo track does not seem to have this problem. The following code does not lock up for me:
MusicTrack tempoTrack;
OSSTATUS = MusicSequenceGetTempoTrack(self.sequence, &tempoTrack);
SafeMusicTrackClear(tempoTrack); //calls into MusicTrackClear
MusicTrackNewExtendedTempoEvent(tempoTrack, 0, self.tempo * self.tempoMultiplier);
MIDIMetaEvent timeSignatureMetaEvent;
timeSignatureMetaEvent.metaEventType = 0x58;
timeSignatureMetaEvent.dataLength = 4;
timeSignatureMetaEvent.data[0] = 1;
timeSignatureMetaEvent.data[1] = 4;
timeSignatureMetaEvent.data[2] = 0x18;
timeSignatureMetaEvent.data[3] = 0x08;
MusicTrackNewMetaEvent(tempoTrack, 0, &timeSignatureMetaEvent);
MusicTrackLoopInfo loopInfo;
loopInfo.loopDuration = 0.25f;
loopInfo.numberOfLoops = 0;
MusicTrackSetProperty(tempoTrack, kSequenceTrackProperty_LoopInfo, &loopInfo, sizeof(loopInfo));
Unfortunately, it does not seem that the tempo track can actually play notes.
UPDATE:
After a few hours of digging around and trying to figure out a better solution to the problem, I settled on manually looping by sending a user event at the end of my sequence.
My sequence is created in a method...
-(void) loadPacketsForLoopingSequence {
SafeMusicTrackClear(loopingTrack); //calls into MusicTrackClear
// calculate timestampToPlaySequenceAt -- the starting point of the current sequence iteration, probably in the past, based on MusicPlayerGetTime and the length of the sequence -- here
// calculate timestampToPlayNextSequenceAt -- the starting point of the next sequence iteration, based on MusicPlayerGetTime and the length of the sequence -- here
// a single iteration of the notes get added to loopingTrack here, starting at timestampToPlaySequenceAt
MusicEventUserData event;
event.length = 1;
event.data[0] = 0xab; //arbitrary designation
// -0.5 to make sure we still have time to do the next step in the callback
MusicTrackNewUserEvent(loopingTrack, timestampToPlayNextSequenceAt - 0.5, &event);
}
...which is called again in the callback:
void sequenceCallback(void* inClientData,
MusicSequence inSequence,
MusicTrack inTrack,
MusicTimeStamp inEventTime,
const MusicEventUserData* inEventData,
MusicTimeStamp inStartSliceBeat,
MusicTimeStamp inEndSliceBeat) {
CSMidiMusicPlayer* musicPlayer = (CSMidiMusicPlayer*)inClientData;
[musicPlayer loadPacketsForLoopingSequence];
}
The callback has to be registered during sequence init using MusicSequenceSetUserCallback.
The -0.5 kludge could probably be eliminated altogether by examining the parameters in sequenceCallback and modifying loadPacketsForLoopingSequence to accept a parameter, but I haven't gotten that far yet.
I like this solution because it stays in MIDI time and doesn't modify the MIDI file in unexpected, stateful ways. (New notes basically get streamed in when you get close enough to a loop marker.)
Related
I have come across a situation where if Time.time is called at two different places at different passes in Update() the value will differ
in its increment thus any use of Vector3(0,Time.time,0) will cause
jumps in the results. I have a gameobject that starts a path in
section of code then transitions to another set of code further along
in the script. The gap execution of Time.time in the first call and
the second call is different than the gaps in the loop of the first
call. That is why I am asking about a replacement. Its not about the code. Its about the Time.time variance between the two Time.time usages. I believe there is an execution caused variance.
void Update () {
synchTime = Time.time;
// This proc releases gameobject from center into an outward spiralling trajectory till the height orbit path attained,
// then disables itself releasing the gameobject into the sine wave orbital path.
if (!reachedElevation)
{
transform.Translate(0, Time.deltaTime, 0);
reachedElevation = true;
_AgentY = Mathf.Sin(synchTime);//Keeps value started and in synch with usage below
}else{
// The trouble is making the 'Y' synch between where the spiral left off and this sine. It has to do with Time.time
_AgentY = Mathf.Sin(synchTime);
Debug.Log("Before transform.localPosition.y: " + transform.localPosition.y);
transform.localPosition = new Vector3(transform.localPosition.x, _AgentY, transform.localPosition.z);
Debug.Log("After transform.localPosition.y: " + transform.localPosition.y);
}
}
Create a local variable at the start of the update call, and set it to Time.time, then you can reference this variable anywhere within the update and it will remain the same.
Ok, so I have some experience with as3 and some of the basics. But this problem has been stumping me for so long. I tried to do a workaround based on what I currently know about as3. But somehow either i get an error message or it just doesn't do anything at all. Here is the code that i'm trying to solve.
var zombieCount:Array = new Array();
var helltime:Timer = new Timer(1500);
helltime.addEventListener(TimerEvent.TIMER, spawnzombies)
helltime.start();
function spawnzombies(happened:TimerEvent){
var zombie1:Zombie = new Zombie();
zombieCount.push(zombie1);
stage.addChild(zombieCount[zombieCount.length - 1]);
zombie1.x = 135 + (330*Math.random())
zombie1.y = -29
stage.addEventListener(Event.ENTER_FRAME, move_zombie)
function move_zombie(happened:Event){
for(var i:int; i < zombieCount.length; i++){
zombieCount[i].y = zombieCount[i].y + 1;
if(zombieCount[i].hitTestObject(border)){
stage.removeChild(zombieCount[i]);
zombieCount.shift();
trace(zombieCount.length);
}
}
}
}
While this may not be inclusive of everything wrong, here are at least a few of the issues I see.
Inline function issue:
Inside your timer tick handler (spawnZombies), you create an inline function called move_zombie and then add an enter frame handler that calls that function.
The issue here, is that every tick of the timer, will then create a whole new copy of that function, and add ANOTHER enter frame handler. This will create huge problems after a few timer ticks.
Break that move_zombie function OUT OF the spawn function:
eg:
helltime.addEventListener(TimerEvent.TIMER, spawnzombies)
helltime.start();
stage.addEventListener(Event.ENTER_FRAME, move_zombie);
function move_zombie(......
function spawnzombies(.....
Iteration issue:
In your for loop:
for(var i:int; i < zombieCount.length; i++){
zombieCount[i].y = zombieCount[i].y + 1;
if(zombieCount[i].hitTestObject(border)){
stage.removeChild(zombieCount[i]);
zombieCount.shift();
trace(zombieCount.length);
}
}
You are not initializing your i value. While this will default it to 0, it's still a good idea for readability to initialize it.
So your iterating forward from 0 to the end of the array. However, if your hit test succeeds, you then use the shift method of the array. This removes the first item of the array (irrespective of what value i is at the time). This will remove the wrong item, plus mess up what zombieCount[i] refers to (because the amount of items has now changed after doing shift, so the next iteration zombieCount[i] will be a reference to same item as the previous iteration).
Instead of what you're currently doing, use the splice method to remove, and iterate backwards so your index doesn't get out of whack.
for(var i:int=zombieCount.length-1;i >=0;i--){
zombieCount[i].y += 1; //move it down 1 pixel
if(zombieCount[i].hitTestObject(border)){
stage.removeChild(zombieCount[i]);
zombieCount.splice(i,1); //remove the item at the current index (do this instead of shift)
trace(zombieCount.length);
}
}
so this is my first time here. Have looked around if this question has been answered, and it has, but not clear enough for me. Sorry for the potential double post.
I am trying to get enemies on the screen. They spawn at random positions, as you can see below, but... they spawn on top of their previous spawned enemy. The answers I read include putting it into an array, but I did, right? I'm a bit confused and working on this for too long, might be that too.
Thanks in advance!
function enemySpawn(e:TimerEvent) {
var enemyAxis:int = Math.round(Math.random()); // determines the x or y axis (0 or 1)
var enemyPos1:int = Math.round(Math.random()) * 600; // spawning it outside the stage (0 or 600)
var enemyPos2:int = Math.floor(Math.random() * 600); // spawning it at random around the stage border (0 through 600)
// spawning the enemy at correct axis
addChild(_enemy);
if(enemyAxis == 0){
// we declare 0 = x-axis, then either position 0 or max-width, plus the enemy's width.
_enemy.x = enemyPos1;
_enemy.y = enemyPos2;
} else
if(enemyAxis == 1){
// we declare 1 = y-axis, then either position 0 or max-height, plus the enemy's height.
_enemy.y = enemyPos1;
_enemy.x = enemyPos2;
}
enemyContainer.push(_enemy);
Not sure that I understood you correctly, but calling addChild is not enough to create a new instance of MovieClip. It just adds it to the target container (or does nothing, if the MC is already in that container).
If you want to spawn several enemies simultaneously, you should create new instances of your MC using the new keyword prior to calling addChild:
var enemy:MovieClip = new Enemy();
addChild(enemy);
// _enemies is an array/vector that contains all created enemies;
// it is not necessary, but helps to access the enemy later, when
// the call of enemySpawn() function inside which the enemy was created
// has ended
_enemies.push(enemy);
To be able to do this, you'll need to enable AS3 linkage for the Enemy symbol in the Library. See this answer.
Hey I am using GetUserMedia() to capture audio input from user's microphone. Meanwhile I want to put captured values into an array so I can manipulate with them. I am using the following code but the problem is that my array gets filled with value 128 all the time (I print the results in console for now), and I can't find my mistake. Can someone help me find my mistake?
//create a new context for audio input
context = new webkitAudioContext();
var analyser = null;
var dataarray = [];
getLiveInput = function() {
navigator.webkitGetUserMedia({audio: true},onStream,onStreamError);
};
function onStream(stream)
{
var input = context.createMediaStreamSource(stream);
analyser = context.createAnalyser();
var str = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteTimeDomainData(str);
for (var i = 0; i < str.length; i++) {
var value = str[i];
dataarray.push(value);
console.log(dataarray)
}//end for loop
}//end function
function onStreamError(e) {
console.error('Streaming failed: ', e);
};
The values returned from getByteTimeDomainData are 8 bit integers, from 0 to 255. 128, which is half way, basically means "no signal". It is the equivalent of 0 in PCM audio data from -1 to 1.
But ANYWAY - there are a couple problems:
First, you're never connecting the input to the analyser. You need input.connect(analyser) before you call analyser.getByteTimeDomainData().
The second problem isn't with your code so much as it's just an implementation issue.
Basically, the gotStream function only gets called once - and getByteTimeDomainData only returns data for 1024 samples worth of audio (a tiny fraction of a second). The problem is, this all happens so quickly and for such a short period of time after the stream gets created, that there's no real input yet. Try wrapping the analyser.getByteTimeDomainData() call and the loop that follows it in a 1000ms setTimeout and then whistle into your microphone as soon as you give the browser permission to record. You should see some values other than 128.
Here's an example: http://jsbin.com/avasav/5/edit
I'm developing a simulator of sorts as a hobby project. The specific function i'm having trouble with takes a row from a matrix and supplies to a function every 50'th millisecond, but I'm a novice with Matlab scripting and need some help.
Each time the timer clicks, the next row in the matrix should be supplied to the function "simulate_datapoint()". Simulate_datapoint() takes the row, performs some calculation magic and updates a complex "track" object in the tracks array.
Is this a completely backwards way of trying to solve this problem or am I close to a working solution? Any help would be greatly appreciated.
Here's what I have right now that doesn't work:
function simulate_data(data)
if ~ismatrix(data)
error('Input must be a matrix.')
end
tracks = tracks_init(); % create an array of 64 Track objects.
data_size = size(data,1); % number of rows in data.
i = 0;
running = 1;
t = timer('StartDelay', 1, 'Period', 0.05, 'TasksToExecute', data_size, ...
'ExecutionMode', 'fixedRate');
t.StopFcn = 'running = 0;';
t.TimerFcn = 'i = i+1; simulate_datapoint(tracks, data(i,:));';
disp('Starting timer.')
start(t);
while(running==1)
% do nothing, wait for timer to finish.
end
delete(t);
disp('Execution complete.')
end
You're very close to a working prototype. A few notes.
1) Your string specified MATLAB functions for the timerFn and stopFn don't share the same memory address, so the variable "i" is meaningless and not shared across them
2) Use waitfor(myTimer) to wait... for the timer.
The following code should get you started, where I used "nested functions" which do share scope with the calling function, so they know about and share variables with the calling scope:
function simulate
iterCount = 0;
running = true;
t = timer('StartDelay', 1, 'Period', 0.05, 'TasksToExecute', 10, ...
'ExecutionMode', 'fixedRate');
t.StopFcn = #(source,event)myStopFn;
t.TimerFcn = #(source,event)myTimerFn;
disp('Starting timer.')
start(t);
waitfor(t);
delete(t);
disp('Execution complete.')
% These are NESTED functions, they have access to variables in the
% calling function's (simulate's) scope, like "iterCount"
% See: http://www.mathworks.com/help/matlab/matlab_prog/nested-functions.html
function myStopFn
running = false;
end
function myTimerFn
iterCount = iterCount + 1;
disp(iterCount);
end
end