/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var GridCell = Container.expand(function (trackIndex, stepIndex, soundId) {
var self = Container.call(this);
self.trackIndex = trackIndex;
self.stepIndex = stepIndex;
self.soundId = soundId;
self.isActive = false;
var cellBg = self.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5
});
var activeBeat = self.attachAsset('activeBeat', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
self.setActive = function (active) {
self.isActive = active;
if (active) {
activeBeat.alpha = 1;
tween(activeBeat, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
easing: tween.easeOut
});
tween(activeBeat, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeIn
});
} else {
activeBeat.alpha = 0;
}
};
self.highlight = function () {
if (self.isActive) {
tween(activeBeat, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 50,
easing: tween.easeOut
});
tween(activeBeat, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeIn
});
}
};
self.down = function (x, y, obj) {
self.setActive(!self.isActive);
if (self.isActive) {
LK.getSound(self.soundId).play();
}
};
return self;
});
var Track = Container.expand(function (trackIndex, soundId, color, name) {
var self = Container.call(this);
self.trackIndex = trackIndex;
self.soundId = soundId;
self.cells = [];
var trackBg = self.attachAsset('trackBackground', {
anchorX: 0,
anchorY: 0.5,
tint: color
});
trackBg.alpha = 0.3;
trackBg.y = 0;
var trackLabel = new Text2(name, {
size: 32,
fill: 0xFFFFFF
});
trackLabel.anchor.set(0, 0.5);
trackLabel.x = -30;
trackLabel.y = 0;
self.addChild(trackLabel);
for (var i = 0; i < 12; i++) {
var cell = new GridCell(trackIndex, i, soundId);
cell.x = 150 + i * 160;
cell.y = 0;
self.addChild(cell);
self.cells.push(cell);
}
self.playStep = function (stepIndex) {
if (self.cells[stepIndex].isActive) {
LK.getSound(self.soundId).play();
self.cells[stepIndex].highlight();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0d1117
});
/****
* Game Code
****/
// Initialize sound assets for each track
var isPlaying = false;
var currentStep = 0;
var bpm = 115;
var stepDuration = 60 / bpm / 4 * 1000;
var lastStepTime = 0;
var tracks = [];
var trackData = [{
soundId: 'kick',
color: 0xFF5722,
name: 'KICK'
}, {
soundId: 'snare',
color: 0x2196F3,
name: 'SNARE'
}, {
soundId: 'hihat',
color: 0xFFC107,
name: 'HI-HAT'
}, {
soundId: 'bass',
color: 0x9C27B0,
name: 'BASS'
}, {
soundId: 'synth',
color: 0x00BCD4,
name: 'RIM'
}, {
soundId: 'lead',
color: 0x8BC34A,
name: 'CLAP'
}];
// Create tracks - center vertically on screen
var totalTracksHeight = trackData.length * 100;
var startY = (2732 - totalTracksHeight) / 2;
for (var i = 0; i < trackData.length; i++) {
var track = new Track(i, trackData[i].soundId, trackData[i].color, trackData[i].name);
track.x = 32;
track.y = startY + i * 100;
game.addChild(track);
tracks.push(track);
}
// Create playhead
var playhead = game.attachAsset('playhead', {
anchorX: 0.5,
anchorY: 0
});
playhead.x = 150 + 32;
playhead.y = startY - 50;
// Create play/pause button
var playButton = game.attachAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
});
playButton.x = 1024;
playButton.y = 350;
var pauseButton = game.attachAsset('pauseButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
pauseButton.x = 1024;
pauseButton.y = 350;
// BPM display
var bpmText = new Text2('BPM: ' + bpm, {
size: 48,
fill: 0xFFFFFF
});
bpmText.anchor.set(0.5, 0.5);
bpmText.x = 1024;
bpmText.y = 500;
game.addChild(bpmText);
// Step counter
var stepText = new Text2('Step: 1/12', {
size: 48,
fill: 0xFFFFFF
});
stepText.anchor.set(0.5, 0.5);
stepText.x = 1024;
stepText.y = 600;
game.addChild(stepText);
// BPM controls
var bpmUpButton = new Text2('+', {
size: 100,
fill: 0x4CAF50
});
bpmUpButton.anchor.set(0.5, 0.5);
bpmUpButton.x = 1100;
bpmUpButton.y = 700;
game.addChild(bpmUpButton);
var bpmDownButton = new Text2('-', {
size: 100,
fill: 0xFF5722
});
bpmDownButton.anchor.set(0.5, 0.5);
bpmDownButton.x = 950;
bpmDownButton.y = 700;
game.addChild(bpmDownButton);
function togglePlayback() {
isPlaying = !isPlaying;
if (isPlaying) {
playButton.alpha = 0;
pauseButton.alpha = 1;
lastStepTime = Date.now();
} else {
playButton.alpha = 1;
pauseButton.alpha = 0;
}
}
function changeBPM(delta) {
bpm = Math.max(60, Math.min(200, bpm + delta));
stepDuration = 60 / bpm / 4 * 1000;
bpmText.setText('BPM: ' + bpm);
}
playButton.down = function (x, y, obj) {
togglePlayback();
};
pauseButton.down = function (x, y, obj) {
togglePlayback();
};
bpmUpButton.down = function (x, y, obj) {
changeBPM(5);
tween(bpmUpButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100
});
tween(bpmUpButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
};
bpmDownButton.down = function (x, y, obj) {
changeBPM(-5);
tween(bpmDownButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100
});
tween(bpmDownButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
};
// Load saved pattern - handle flattened storage structure
var savedPattern = storage.pattern || {};
for (var trackIndex = 0; trackIndex < tracks.length; trackIndex++) {
for (var stepIndex = 0; stepIndex < 12; stepIndex++) {
var key = trackIndex + '_' + stepIndex;
if (savedPattern[key]) {
tracks[trackIndex].cells[stepIndex].setActive(true);
}
}
}
game.update = function () {
if (isPlaying) {
var currentTime = Date.now();
if (currentTime - lastStepTime >= stepDuration) {
// Play current step
for (var i = 0; i < tracks.length; i++) {
tracks[i].playStep(currentStep);
}
// Move to next step
currentStep = (currentStep + 1) % 12;
stepText.setText('Step: ' + (currentStep + 1) + '/12');
// Update playhead position
playhead.x = 150 + 32 + currentStep * 160;
lastStepTime = currentTime;
// Auto-save pattern - flatten to single depth for storage compatibility
var pattern = {};
for (var trackIndex = 0; trackIndex < tracks.length; trackIndex++) {
for (var stepIndex = 0; stepIndex < 12; stepIndex++) {
var key = trackIndex + '_' + stepIndex;
pattern[key] = tracks[trackIndex].cells[stepIndex].isActive;
}
}
storage.pattern = pattern;
}
}
};
// Title
var titleText = new Text2('BM Studio', {
size: 80,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 100;
game.addChild(titleText);
// Instructions
var instructText = new Text2('Tap grid to add beats • Press play to start', {
size: 20,
fill: 0x888888
});
instructText.anchor.set(0.5, 0);
instructText.x = 1024;
instructText.y = 2600;
game.addChild(instructText); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var GridCell = Container.expand(function (trackIndex, stepIndex, soundId) {
var self = Container.call(this);
self.trackIndex = trackIndex;
self.stepIndex = stepIndex;
self.soundId = soundId;
self.isActive = false;
var cellBg = self.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5
});
var activeBeat = self.attachAsset('activeBeat', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
self.setActive = function (active) {
self.isActive = active;
if (active) {
activeBeat.alpha = 1;
tween(activeBeat, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
easing: tween.easeOut
});
tween(activeBeat, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeIn
});
} else {
activeBeat.alpha = 0;
}
};
self.highlight = function () {
if (self.isActive) {
tween(activeBeat, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 50,
easing: tween.easeOut
});
tween(activeBeat, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeIn
});
}
};
self.down = function (x, y, obj) {
self.setActive(!self.isActive);
if (self.isActive) {
LK.getSound(self.soundId).play();
}
};
return self;
});
var Track = Container.expand(function (trackIndex, soundId, color, name) {
var self = Container.call(this);
self.trackIndex = trackIndex;
self.soundId = soundId;
self.cells = [];
var trackBg = self.attachAsset('trackBackground', {
anchorX: 0,
anchorY: 0.5,
tint: color
});
trackBg.alpha = 0.3;
trackBg.y = 0;
var trackLabel = new Text2(name, {
size: 32,
fill: 0xFFFFFF
});
trackLabel.anchor.set(0, 0.5);
trackLabel.x = -30;
trackLabel.y = 0;
self.addChild(trackLabel);
for (var i = 0; i < 12; i++) {
var cell = new GridCell(trackIndex, i, soundId);
cell.x = 150 + i * 160;
cell.y = 0;
self.addChild(cell);
self.cells.push(cell);
}
self.playStep = function (stepIndex) {
if (self.cells[stepIndex].isActive) {
LK.getSound(self.soundId).play();
self.cells[stepIndex].highlight();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0d1117
});
/****
* Game Code
****/
// Initialize sound assets for each track
var isPlaying = false;
var currentStep = 0;
var bpm = 115;
var stepDuration = 60 / bpm / 4 * 1000;
var lastStepTime = 0;
var tracks = [];
var trackData = [{
soundId: 'kick',
color: 0xFF5722,
name: 'KICK'
}, {
soundId: 'snare',
color: 0x2196F3,
name: 'SNARE'
}, {
soundId: 'hihat',
color: 0xFFC107,
name: 'HI-HAT'
}, {
soundId: 'bass',
color: 0x9C27B0,
name: 'BASS'
}, {
soundId: 'synth',
color: 0x00BCD4,
name: 'RIM'
}, {
soundId: 'lead',
color: 0x8BC34A,
name: 'CLAP'
}];
// Create tracks - center vertically on screen
var totalTracksHeight = trackData.length * 100;
var startY = (2732 - totalTracksHeight) / 2;
for (var i = 0; i < trackData.length; i++) {
var track = new Track(i, trackData[i].soundId, trackData[i].color, trackData[i].name);
track.x = 32;
track.y = startY + i * 100;
game.addChild(track);
tracks.push(track);
}
// Create playhead
var playhead = game.attachAsset('playhead', {
anchorX: 0.5,
anchorY: 0
});
playhead.x = 150 + 32;
playhead.y = startY - 50;
// Create play/pause button
var playButton = game.attachAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
});
playButton.x = 1024;
playButton.y = 350;
var pauseButton = game.attachAsset('pauseButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
pauseButton.x = 1024;
pauseButton.y = 350;
// BPM display
var bpmText = new Text2('BPM: ' + bpm, {
size: 48,
fill: 0xFFFFFF
});
bpmText.anchor.set(0.5, 0.5);
bpmText.x = 1024;
bpmText.y = 500;
game.addChild(bpmText);
// Step counter
var stepText = new Text2('Step: 1/12', {
size: 48,
fill: 0xFFFFFF
});
stepText.anchor.set(0.5, 0.5);
stepText.x = 1024;
stepText.y = 600;
game.addChild(stepText);
// BPM controls
var bpmUpButton = new Text2('+', {
size: 100,
fill: 0x4CAF50
});
bpmUpButton.anchor.set(0.5, 0.5);
bpmUpButton.x = 1100;
bpmUpButton.y = 700;
game.addChild(bpmUpButton);
var bpmDownButton = new Text2('-', {
size: 100,
fill: 0xFF5722
});
bpmDownButton.anchor.set(0.5, 0.5);
bpmDownButton.x = 950;
bpmDownButton.y = 700;
game.addChild(bpmDownButton);
function togglePlayback() {
isPlaying = !isPlaying;
if (isPlaying) {
playButton.alpha = 0;
pauseButton.alpha = 1;
lastStepTime = Date.now();
} else {
playButton.alpha = 1;
pauseButton.alpha = 0;
}
}
function changeBPM(delta) {
bpm = Math.max(60, Math.min(200, bpm + delta));
stepDuration = 60 / bpm / 4 * 1000;
bpmText.setText('BPM: ' + bpm);
}
playButton.down = function (x, y, obj) {
togglePlayback();
};
pauseButton.down = function (x, y, obj) {
togglePlayback();
};
bpmUpButton.down = function (x, y, obj) {
changeBPM(5);
tween(bpmUpButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100
});
tween(bpmUpButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
};
bpmDownButton.down = function (x, y, obj) {
changeBPM(-5);
tween(bpmDownButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100
});
tween(bpmDownButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
};
// Load saved pattern - handle flattened storage structure
var savedPattern = storage.pattern || {};
for (var trackIndex = 0; trackIndex < tracks.length; trackIndex++) {
for (var stepIndex = 0; stepIndex < 12; stepIndex++) {
var key = trackIndex + '_' + stepIndex;
if (savedPattern[key]) {
tracks[trackIndex].cells[stepIndex].setActive(true);
}
}
}
game.update = function () {
if (isPlaying) {
var currentTime = Date.now();
if (currentTime - lastStepTime >= stepDuration) {
// Play current step
for (var i = 0; i < tracks.length; i++) {
tracks[i].playStep(currentStep);
}
// Move to next step
currentStep = (currentStep + 1) % 12;
stepText.setText('Step: ' + (currentStep + 1) + '/12');
// Update playhead position
playhead.x = 150 + 32 + currentStep * 160;
lastStepTime = currentTime;
// Auto-save pattern - flatten to single depth for storage compatibility
var pattern = {};
for (var trackIndex = 0; trackIndex < tracks.length; trackIndex++) {
for (var stepIndex = 0; stepIndex < 12; stepIndex++) {
var key = trackIndex + '_' + stepIndex;
pattern[key] = tracks[trackIndex].cells[stepIndex].isActive;
}
}
storage.pattern = pattern;
}
}
};
// Title
var titleText = new Text2('BM Studio', {
size: 80,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 100;
game.addChild(titleText);
// Instructions
var instructText = new Text2('Tap grid to add beats • Press play to start', {
size: 20,
fill: 0x888888
});
instructText.anchor.set(0.5, 0);
instructText.x = 1024;
instructText.y = 2600;
game.addChild(instructText);