Since music speed and pitch is coupled together, if I speed up music, pitch is also increased. And conversely, if I slow down music, pitch is also decreased.
However, I saw that using granular synthesis, I can decouple speed and pitch. So, I'm currently trying hard to implement granular synthesis.
First of all, I think I succeeded in implementing double speed and half speed, while pitch is same. The code is as same as the following:
※ grain size is 2000. It means that I use 0.04ms of sound as one grain. (2000 samples * 1 s / 44100 samples = 0.04s = 40ms)
// get music
const $fileInput = document.createElement('input');
$fileInput.setAttribute('type', 'file');
document.body.appendChild($fileInput);
$fileInput.addEventListener('change', async (e) => {
const music = await $fileInput.files[0].arrayBuffer();
const actx = new (window.AudioContext || window.webkitAudioContext)({ latencyHint: 'playback', sampleRate: 44100 });
const audioData = await actx.decodeAudioData(music);
const original = audioData.getChannelData(0);
const arr = [];
const grainSize = 2000;
// Please choose one code out of double speed code or half speed code
// copy and paste audio processing code here
});
// double speed
// ex: [0,1,2,3, 4,5,6,7, 8] => [0,1, 4,5, 8] discard 2 items out of 4 items
for (let i = 0; i < original.length; i += grainSize) {
if (original[i + (grainSize / 2) - 1] !== undefined) {
for (let j = 0; j < grainSize / 2; j++) {
arr.push(original[i + j]);
}
} else {
for (let j = i; j < original.length; j++) {
arr.push(j);
}
}
}
// half speed
// ex: [0,1, 2,3, 4] => [0,1,0,0, 2,3,0,0, 4,0,0] add 'two' zeros after every 'two' items
for (let i = 0; i < original.length; i += grainSize) {
if (original[i + grainSize - 1] !== undefined) {
for (let j = 0; j < grainSize; j++) {
arr.push(original[i + j]);
}
} else {
for (let j = i; j < original.length; j++) {
arr.push(original[j]);
}
}
for (let j = 0; j < grainSize; j++) {
arr.push(0);
}
}
// play sound
const f32Arr = Float32Array.from(arr);
const audioBuffer = new AudioBuffer({ length: arr.length, numberOfChannels: 1, sampleRate: actx.sampleRate });
audioBuffer.copyToChannel(f32Arr, 0);
const absn = new AudioBufferSourceNode(actx, { buffer: audioBuffer });
absn.connect(actx.destination);
absn.start();
But the problem is, I totally have no idea how to implement pitch shifter (that is, different pitch, same speed).
As far as I think, same speed means same AudioBuffer size. Therefore, the only variable in my hand is grain size. But I seriously don't know what should I do. It would be greatly appreciated if you share some of your knowledge. Thank you very much!
To Phil Freihofner
Hello, thank you for the kind explanation. I tried your method. As far as I understand, your method is a process that does the following:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] // input data (10 samples)
→ [0, 2, 4, 6, 8] // double speed, 1 octave high (sampling interval: 2)
→ [0, 0, 2, 2, 4, 4, 6, 6, 8, 8] // change duration
The result sounds 1 octave high with same duration (successful pitch shifting). However, I don't know what should I do if I do sampling from the input data with sampling interval 1.5? What I mean is I have no idea how to make the length of [0, 1, 3, 4, 6, 7, 9] as the same length with the input data.
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] // input data
// from
[0, 1, 3, 4, 6, 7, 9] // sampling interval: 1.5
// to
[?, ?, ?, ?, ?, ?, ?, ?, ?, ?]
Meanwhile, I learned that pitch shift can be achieved by a way and as far as I understand, the way is as following:
[1] make each granule be started at the same position as original source
[2] play each granule with different speed.
In addition, I found that I can achieve pitch shifting and time stretching if I transform an input data like the following:
input data = [0, 1, 2, 3, 4, 5, 6, 7]
grain size = 4
<pitch shifting>
in case of p = 2
result = [0, 2, 0, 2, 4, 6, 4, 6] // sounds like one octave high
// If I remember correctly, [0, 2, 0, 0, 4, 6, 0, 0] is also fine
// (and it is more fit to the definition above ([1] and [2])
// but the sound was not good (stuttering).
// I found that [0, 2, 0, 2...] is better.
in case of p = 1.5
result = [0, 1, 3, 0, 4, 5, 7, 4]
in case of p = 0.5
result = [0, 0, 1, 1, 4, 4, 5, 5] // sounds like one octave low
<time stretching>
in case of speed = 2
result = [0, 1, 4, 5]
in case of speed = 1.2
result = [0, 1, 2, 4, 5, 6]
in case of speed = 0.5
result = [0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 6, 7]
// If I remember correctly, [0, 1, 2, 3, 0, 0, 0, 0...] is also fine
// but the sound was not good (stuttering)
in case of speed = 0.75
result = [0, 1, 2, 3, 0, 4, 5, 6, 7, 4]
Anyway, thank you for the answer.
I've not read your code close enough to comment on it specifically, but I can comment on the general theory of how pitch shifting is accomplished.
The granules are usually given volume envelopes, with a fade-in and fade-out. I've seen the Hann function (Hanning Window) mentioned as a possibility. Also, the granules are overlapped, with the windowing creating a cross-fade, in effect.
Let's say a granule is 2000 frames, but with the windowing. If you make a granule at every 1000 frames and play them back, overlapping, at the same spacing (every 1000 frames), you should hear the equivalent of the original sound.
Varying the playback distance between the overlapping granules is how the different time lengths of the sound are accomplished. For example, instead of playing a granule every 1000 frames, use 900 or 1100.
I'm pretty sure there are factors to take into consideration concerning the size and shape of the windowing and the range of possible intervals between the granules, but I am not up on them. My simple experiments with this have been with Java and mostly work, but with some artificiality creeping into the playback.
I think consulting at StackOverflow's Signal Processing site would be a good bet for getting more info on the specifics.
EDIT: I just realized that I misread your question! You were asking about changing the pitch while retaining the length of time over which the sounds play. I don't know if this is the "best" way, but I'd consider a plan of doing this in two steps. First, change the sound to the desired pitch. Then, alter the duration of the new sound to be that of the original sound.
The first step can be done with linear interpolation. I tried to explain how to do this in a previous question. For the second step, we break the transformed wave into granules.
However, I just noticed, Spektre has an additional answer on that post that does directly using what you ask, via using FFT. This is probably a better way, but I haven't tried implementing it myself.
EDIT 2, in response to the question added to the OP:
Given, PCM data for 10 frames as follows [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] (I'm using signed floats ranging from -1 to 1. You may have to scale this to convert to your format.)
To change the pitch of the playback to 1.5x (but also changes the length) we get the following data: [0, 0.15, 0.3, 0.45, 0.6, 0.75, 0.9]
The 0.15 is a value that is halfway between points 0.1 and 0.2, arrived at by linear interpolation. If the speed were 1.25x, the data points would be as follows: [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, ?? (depends on what follows 0.9)].
The linear intepolation calculation for index 1 in the sequence is as follows:
pitchShiftedAudioData1 = originalPCM1 * (1 - 0.25) + originalPCM2 * 0.75;
In other words, since we land at a point that is 0.25 of the way in between originalPCM1 and originalPCM2, the above calculates what that value would be if the data progressed linearly from 1 to 2.
After doing all this, there would still remain additional steps to form the pitch-shifted data into granules. One has to use a windowing function for each granule. If the window were only 10 frames long (far too short, but will illustrate), a possible window might be the following: [0.01, 0.15 , 0.5 , 0.85 , 1, 1, 0.85, 0.5, 0.15 , 0.01]. (In actuality, it should follow the Hann function.)
This is applied to the data from the different starting points, to create the granules, where N is the index in the array of the signal.
[ signal[N] * window[0], signal[N+1] * window1, signal[N+2] * window2, ..., signal[N+10] * window[10] ]
To create the new signal, the resulting granules are placed sequentially, overlapping, and summed. The relative placement of the granules (how close together or far apart) determines the timing. This is my naive understanding of a brute-force way to accomplish time-shifting, and I've had some OK, not great, results.
I hope this clarifies what I was attempting to describe somewhat!
If you aren't able to follow, please consider unchecking this as the answer. Others may participate that will be able to provide easier to understand information or corrections.
Time-shifting is pretty advanced, IMHO, so expect some complicated calculations and coding (unless someone has a tool to recommend).
I am using phaser3. When I create a group of sprites with values for gravity they are not moving. If the sprites are created individually then they do move.
For example, the code below works:
brick1 = game.add.sprite(game.world.width / 2, 0, 'tile'+randomNumber(1,6));
brick2 = game.add.sprite((game.world.width / 2) + 60, 0, 'tile'+randomNumber(1,6));
game.physics.arcade.enable(brick1);
game.physics.arcade.enable(brick2);
brick1.enableBody=true;
brick2.enableBody=true;
brick1.body.gravity.y = 10;
brick2.body.gravity.y = 10;
I need them to be in a group so if I have the code below, they just don't move. I have checked the attributes on each child item and they have values for gravity.
brick = game.add.group();
brick1 = brick.create(game.world.width / 2, 0, 'tile'+randomNumber(1,6));
brick2 = brick.create((game.world.width / 2)+60, 0, 'tile'+randomNumber(1,6));
game.physics.arcade.enable(brick);
game.physics.arcade.enable(brick1);
game.physics.arcade.enable(brick2);
brick1.enableBody=true;
brick2.enableBody=true;
brick1.body.gravity.y = 10;
brick2.body.gravity.y = 10;
console.log(brick);
UPDATE:
I realise that this is happening when setting the velocity of each child in the group. I want to have the user press the down cursor key and then change the velocity. I have the code below, but this just stops them from moving at all.
if (!cursors.down.isDown) {
brick.children.forEach(child => child.body.setVelocity(300));
} else {
brick.children.forEach(child => child.body.setVelocity(300));
}
Also tried with "brick.children.forEach(child => child.body.velocity = 300);", but no luck.
I tried to make your code working with as few corrections as possible. I guess variable game in your example points to the current scene, so i called it scene:
let brick = scene.add.group();
let brick1 = brick.create(game.world.width / 2, 0, 'tile'+randomNumber(1,6));
let brick2 = brick.create((game.world.width / 2)+60, 0, 'tile'+randomNumber(1,6));
scene.physics.add.existing(brick1);
scene.physics.add.existing(brick2);
brick1.enableBody=true;
brick2.enableBody=true;
brick1.body.gravity.y = 10;
brick2.body.gravity.y = 10;
I've hit a mental block of sorts, and was looking for some advice or suggestions. My problem is this:
I have a WebGL scene (I'm not using a 3rd party library, except gl-matrix), in which the user can rotate the camera up/down and left/right (rotate around X/Y axis). They can also rotate the model as well (yaw/pitch).
To see the problem, imagine the model has two blocks, A and B in the scene, with A at the center and B to the right (in the viewport), and the rotation center in the center of A. If the user rotates the model, it rotates about the center of block A. But if the user clicks on object B, I need to be able to change the center of rotation to B's center, but still maintain the current camera orientation. Currently, when the center of rotation switches to B, block B moves to the center of the screen, and block A moves to the left. Basically, the code always centers on the current center or rotation.
I use the following code for the modelview matrix update:
var mvMatrix = this.mvMatrix;
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, mvMatrix, this.orbit);
mat4.rotateY(mvMatrix, mvMatrix, this.orbitYaw);
mat4.rotateX(mvMatrix, mvMatrix, this.orbitPitch);
mat4.translate(mvMatrix, mvMatrix, this.eye);
mat4.rotateY(mvMatrix, mvMatrix, this.eyeYaw);
mat4.rotateX(mvMatrix, mvMatrix, this.eyePitch);
I'm trying to figure out what the right yaw and pitch values for orbit and eye I should use in order to move back the current location and to achieve the present camera/eye orientation to avoid the "bounce" from one object to another as the rotation center moves.
I've searched a lot and can't seem to find how best to do this (my current attempt(s) have issues). Any sample code, or just good descriptions would be appreciated.
Edit
I followed gman's advice and tried the following code, but switching orbits just jumped around. My model is composed of multiple objects, and the orbit center can change, but after changing orbits, the orientation of the camera needs to remain steady, which is why I have to calculate the correction to the orbit yaw/pitch and eye yaw/pitch to put the eye back in the same spot and pointing in the same direction after changing orbits. BTW, I only have one orbit yaw and pitch, based on where the current orbit is, so that's a little different from gman's sample:
Camera.prototype.changeOrbit = function (newOrbit) {
var matA = mat4.create();
var matB = mat4.create();
mat4.translate(matA, matA, this.orbit);
mat4.rotateY(matA, matA, this.orbitYaw);
mat4.rotateX(matA, matA, this.orbitPitch);
mat4.translate(matB, matB, newOrbit);
mat4.rotateY(matB, matB, this.orbitYaw);
mat4.rotateX(matB, matB, this.orbitPitch);
var matInverseNewOrbit = mat4.create();
var matNewOrbitToCamera = mat4.create();
mat4.invert(matInverseNewOrbit, matB);
mat4.multiply(matNewOrbitToCamera, matInverseNewOrbit, matA);
var m = matNewOrbitToCamera;
this.eye[0] = m[12];
this.eye[1] = m[13];
this.eye[2] = m[14];
this.eyePitch = ExtractPitch(m);
this.eyeYaw = ExtractYaw(m);
this.update();
};
ExtractPitch and ExtractYaw work as gman had specified, but I do rotate around different axes since pitch is normally defined around the Y axis, and so on. Thanks for the suggestions, though.
I'm not sure I can explain this but basically:
When switching from A to B, at switch time,
Compute the matrix for the camera going around A (the code you have above). (camera)
Compute the matrix for B (matB)
Compute the inverse of the matrix for B. (inverseMatB)
Multiply camera by inverseMatB. (matBtoCamera)
You now have a matrix that goes from B to the camera.
Decompose this matrix (matBToCamera) back into translation and rotation.
Unfortunately I don't know of a good decompose matrix function to point you at. I haven't needed one in a long time. Translation is basically elements 12, 13, 14 of your matrix. (Assuming you are using 16 element matrices which I think is what glMatrix uses).
var translation = [m[12], m[13], m[14]];
For rotation the upper/left 3x3 part of the matrix represents rotation. As long as there is no scaling or skewing involved, according to this page (http://nghiaho.com/?page_id=846) it's
var rotXInRadians = Math.atan2(m[9], m[10]);
var rotYInRadians = Math.atan2(-m[8], Math.sqrt(m[9] * m[9] + m[10] * m[10]));
var rotZInRadians = Math.atan2(m[4], m[0]);
Here's an example
http://jsfiddle.net/greggman/q7Bsy/
I'll paste the code here specific to glMatrix
// first let's make 3 nodes, 'a', 'b', and 'camera
var degToRad = function(v) {
return v * Math.PI / 180;
}
var a = {
name: "a",
translation: [0, -50, -75],
pitch: 0,
yaw: degToRad(30),
};
var b = {
name: "b",
translation: [0, 100, 50],
pitch: 0,
yaw: degToRad(-75),
}
var camera = {
name: "cam",
translation: [0, 15, 10],
pitch: 0,
yaw: degToRad(16),
parent: a,
};
Here's the code that computes the matrix of each
var matA = mat4.create();
mat4.identity(matA);
mat4.translate(matA, matA, a.translation);
mat4.rotateY(matA, matA, a.pitch);
mat4.rotateX(matA, matA, a.yaw);
a.mat = matA;
var matB = mat4.create();
mat4.identity(matB);
mat4.translate(matB, matB, b.translation);
mat4.rotateY(matB, matB, b.pitch);
mat4.rotateX(matB, matB, b.yaw);
b.mat = matB;
var matCamera = mat4.create();
mat4.identity(matCamera);
var parent = camera.parent;
mat4.translate(matCamera, matCamera, parent.translation);
mat4.rotateY(matCamera, matCamera, parent.pitch);
mat4.rotateX(matCamera, matCamera, parent.yaw);
mat4.translate(matCamera, matCamera, camera.translation);
mat4.rotateY(matCamera, matCamera, camera.pitch);
mat4.rotateX(matCamera, matCamera, camera.yaw);
camera.mat = matCamera;
and here's the code that swaps cameras
// Note: Assumes matrices on objects are updated.
var reparentObject = function(obj, newParent) {
var matInverseNewParent = mat4.create();
var matNewParentToObject = mat4.create();
mat4.invert(matInverseNewParent, newParent.mat);
mat4.multiply(matNewParentToObject, matInverseNewParent, obj.mat);
var m = matNewParentToObject;
obj.translation[0] = m[12];
obj.translation[1] = m[13];
obj.translation[2] = m[14];
var rotXInRadians = Math.atan2(m[9], m[10]);
var rotYInRadians = Math.atan2(-m[8], Math.sqrt(m[9] * m[9] + m[10] * m[10]));
var rotZInRadians = Math.atan2(m[4], m[0]);
obj.pitch = rotYInRadians;
obj.yaw = rotXInRadians;
obj.parent = newParent;
};
var newParent = camera.parent == a ? b : a;
reparentObject(camera, newParent);
Lifting block on prismatic joint (b2PrismaticJointDef) on upper point gives impulse to the dynamic body that lays on lifting block surface even if lifting block doesn't move at all.
Maybe it's bug of javascript port. But I want to fix it because I need of elevators in my game.
Update v1
I have used box2dweb javascript port of Box2DFlash 2.1a https://code.google.com/p/box2dweb/.
Update v2
Here is similar demo on flash http://hyzhak.github.com/darlingjs/performance/box2dweb/ it has same problems, so maybe this issue on flash or on original Box2D engine.
http://jsfiddle.net/hyzhak/2kjDZ/
var b2Vec2 = Box2D.Common.Math.b2Vec2,
b2BodyDef = Box2D.Dynamics.b2BodyDef,
b2Body = Box2D.Dynamics.b2Body,
b2FixtureDef = Box2D.Dynamics.b2FixtureDef,
b2World = Box2D.Dynamics.b2World,
b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape,
b2CircleShape = Box2D.Collision.Shapes.b2CircleShape,
b2DebugDraw = Box2D.Dynamics.b2DebugDraw,
b2PrismaticJointDef = Box2D.Dynamics.Joints.b2PrismaticJointDef;
(function() {
var world = buildWorld();
var leftBlock = buildBlock({world: world, x: 2, y: 8, width: 2, height: 12, static: true});
var rightBlock = buildBlock({world: world, x: 12, y: 8, width: 2, height: 12, static: true});
var bottomBlock = buildBlock({world: world, x: 7, y: 13, width: 8, height: 2, static: false});
var box = buildBlock({world: world, x: 7, y: 10, width: 2, height: 2, static: false});
var joint = buildPrismaticJoint({world: world,
anchorA: new b2Vec2(7, 13),
axis: new b2Vec2(0, 1),
bodyA: bottomBlock,
bodyB: world.GetGroundBody()});
var debugDraw = buildDebugDraw(world);
setInterval(function(){
world.Step(1 / 60, 10, 10);
world.DrawDebugData();
world.ClearForces();
},1000/60);
})();
function buildWorld() {
return new b2World(
new b2Vec2(0, 10), //gravity vector
true
);
}
function buildBlock(state) {
var fixDef = new b2FixtureDef;
fixDef.shape = new b2PolygonShape;
fixDef.density = 1.0;
fixDef.friction = 0.5;
fixDef.restitution = .5;
fixDef.shape.SetAsBox(state.width / 2, state.height / 2);
var bodyDef = new b2BodyDef;
bodyDef.type = state.static?b2Body.b2_staticBody:b2Body.b2_dynamicBody;
bodyDef.position.Set(state.x, state.y);
var body = state.world.CreateBody(bodyDef);
body.CreateFixture(fixDef);
return body;
}
//buildPrismaticJoint(world, 9, 15, 0, 1, bottomBlock, world.GetGroundBody());
function buildPrismaticJoint(state) {
var jointDef = new b2PrismaticJointDef();
jointDef.Initialize(state.bodyA, state.bodyB, state.anchorA, state.axis);
jointDef.collideConnected = false;
jointDef.lowerTranslation = 0.0;
jointDef.upperTranslation = 5.0;
jointDef.enableLimit = true;
jointDef.maxMotorForce = 400.0;
jointDef.motorSpeed = 3.0;
jointDef.enableMotor = true;
return state.world.CreateJoint(jointDef);
}
function buildDebugDraw(world) {
var debugDraw = new b2DebugDraw();
debugDraw.SetSprite(document.getElementById("playground").getContext("2d"));
debugDraw.SetDrawScale(20.0);
debugDraw.SetFillAlpha(0.5);
debugDraw.SetLineThickness(1.0);
debugDraw.SetFlags(
b2DebugDraw.e_shapeBit |
b2DebugDraw.e_jointBit |
b2DebugDraw.e_aabbBit |
b2DebugDraw.e_pairBit |
b2DebugDraw.e_centerOfMassBit |
b2DebugDraw.e_controllerBit
);
world.SetDebugDraw(debugDraw);
return debugDraw;
}
I had the same issue using the Box2D v2.2.1 C++ code. I'm using a b2PrismaticJoint between a static body and a moving (dynamic) body to simulate a "lift" (or elevator).
When the lift reaches the upper end of translation along the prismatic joint, it appears to stop. However, when another body is sitting on top of the lift and it reaches the upper end of translation, the body on top the lift begins to "bounce" around as if the lift is still applying a force. (I believe this is exactly what is happening, i.e., the lift's body doesn't translate any further along the prismatic joint, but the prismatic joint's motor is still cranking away and applying upward forces to the body).
I initially resolved this issue by manually setting the prismatic joint's motor speed to 0.0 when the upper end of translation was reached. In my main game loop, i.e., the function that gets called at the frame rate ("tick:" in Box2D or "update:" in Cocos2D), I inserted the following (I'm using Objective-C here and kLiftJointValue is an arbitrary integer constant):
// Iterate over all joints (used to prevent upper end oscillations from psimaticJoints on lifts)
for (b2Joint* j = world->GetJointList(); j; j = j->GetNext())
{
if ((int)(j->GetUserData()) == kLiftJointValue) // check to see if lift is at upper end of translation
{
CGFloat currentTranslation = ((b2PrismaticJoint*)j)->GetJointTranslation();
CGFloat lowerLimit = ((b2PrismaticJoint*)j)->GetLowerLimit();
CGFloat upperLimit = ((b2PrismaticJoint*)j)->GetUpperLimit();
b2Body *bodyA = j->GetBodyA();
b2Body *bodyB = j->GetBodyB();
BOOL notStopped = (bodyA->GetType() == b2_dynamicBody || bodyB->GetType() == b2_dynamicBody);
if (fabsf(currentTranslation - lowerLimit) <= 0.01*(upperLimit - lowerLimit) && notStopped )
{
CCLOG(#"Stopping platform");
(j->GetBodyA())->SetType(b2_staticBody);
(j->GetBodyB())->SetType(b2_staticBody);
((b2PrismaticJoint*)j)->SetMotorSpeed(0.0);
}
}
}
The above code simply iterates over all joints in the world and looks for those tagged appropriately, and if the body has translated to within 1% of the maximum translation allowed then the moving body is changed to static and the motor is disabled by setting the motor speed to 0. You could also use Box2D collision detection and set the joint's motor speed to 0 that way...
I then realized that the best way to implement this lift (without disturbing anything sitting on top of the lift) was to slowly decelerate the prismatic joint's motor speed gradually. This is how I did it:
// Iterate over all joints (used to prevent upper end oscillations from psimaticJoints on lifts)
for (b2Joint* j = world->GetJointList(); j; j = j->GetNext())
{
if ((int)(j->GetUserData()) == kLiftJointValue) // check to see if lift is at upper end of translation
{
CGFloat currentTranslation = ((b2PrismaticJoint*)j)->GetJointTranslation();
CGFloat lowerLimit = ((b2PrismaticJoint*)j)->GetLowerLimit();
CGFloat upperLimit = ((b2PrismaticJoint*)j)->GetUpperLimit();
b2Body *bodyA = j->GetBodyA();
b2Body *bodyB = j->GetBodyB();
BOOL notStopped = (bodyA->GetType() == b2_dynamicBody || bodyB->GetType() == b2_dynamicBody);
if (fabsf(currentTranslation - lowerLimit) <= 0.25*(upperLimit - lowerLimit) && notStopped)
{
// Get current motor speed and update
CGFloat currentSpeed = ((b2PrismaticJoint*)j)->GetMotorSpeed();
CGFloat newSpeed;
if (currentSpeed < 0.0)
{
if (fabsf(currentTranslation - lowerLimit) <= 0.01*(upperLimit - lowerLimit) && notStopped )
{
CCLOG(#"Stopping platform");
(j->GetBodyA())->SetType(b2_staticBody);
(j->GetBodyB())->SetType(b2_staticBody);
((b2PrismaticJoint*)j)->SetMotorSpeed(0.0);
}
else
{
CCLOG(#"Decelerating: speed=%f", currentSpeed);
newSpeed = 0.95*currentSpeed;
}
}
else if (currentSpeed > 0.0)
{
CCLOG(#"Accelerating: speed=%f", currentSpeed);
newSpeed = 1.05*currentSpeed;
if (newSpeed > 20.0)
newSpeed = 20.0;
}
// update motor speed
((b2PrismaticJoint*)j)->SetMotorSpeed(newSpeed);
}
}
}
You'll need to play with the various constants (especially the constants 0.95 and 1.05) since they are fine-tuned for specific motor properties (maxMotorForce = 1000 and motorSpeed = 20 in my case).
Of course elsewhere in my project I have timers running which reset the lift body and joint motor properties as needed (to make it travel up, then back down by reversing the joint's motor speed with a positive value).
I didn't implement the acceleration/deceleration technique when the lift was translated near the other (bottom) limit since I didn't care about stuff riding the lift being imparted forces in this direction when the lift stopped. However, the same approach as outlined above can be used to smoothly accelerate or decelerate as the lift leaves from or returns to the bottom...