ならば

音とかで遊んでいたログ

音のワンキーゲーム

SFCaveというワンキーゲームがある。その音バージョンを作った。

現在の音高の位置を黄色のドットで表しているが、これはイージーモード。本当はこのドットは描かずに聴音だけでやるべきである。が、そうすると自分の場合、開始直後に位置感覚を喪失して全くゲームにならないので仕方ない。


比較。大体こんな感じ。



開発言語は今回もChucKとProcessing。

ChucKのプログラム。一回起動したら裏で動かしっぱなし。

BeeThree b => Gain g => dac;
.3 => g.gain;
1 => b.noteOff;

OscRecv recv;
12000 => recv.port;
recv.listen();
recv.event("/pitch, f") @=> OscEvent oe;

false => int isPlaying;
float pitch;
while (oe => now) {
    while (oe.nextMsg() != 0) {  
        oe.getFloat() => pitch;
        if (pitch < 0) {
            if (isPlaying) {
                false => isPlaying;
                1 => b.noteOff;
            } else {
                true => isPlaying;
                1 => b.noteOn;
            }
        } else {
            pitch => Std.mtof => b.freq;
        }
    }
}


Processingのプログラム。操作方法:Enterでスタート、↑キーで上昇。

import oscP5.*;
import netP5.*;

static int FPS = 20;
static float GRAVITY = -0.02;
static int MAX_DELTA = 5;
static int MAX_DIFF = 3;
static int MIN_SPACE = 9;
static int UPPER_BOUND = 95;
static int LOWER_BOUND = 36;
static int NUM_NOTES = 40;
static int NOTE_WIDTH = 15;
static int NOTE_HEIGHT = 10;
static float SPEED_UP = 0.15;

boolean isEasy = true;
boolean isPlaying;
float pitch, dp;
float time, dt;
int current, oldest;
int uppers[], lowers[];
float center;
int score;
OscP5 osc;
NetAddress addr;
int port = 12000;
PFont gfont, sfont;
PImage piano;

void setup() {
  noLoop();
  isPlaying = false;
  uppers = new int[NUM_NOTES];
  lowers = new int[NUM_NOTES];
  size(NUM_NOTES * NOTE_WIDTH, (UPPER_BOUND - LOWER_BOUND + 1) * NOTE_HEIGHT);
  frameRate(FPS);
  osc = new OscP5(this, port);
  addr = new NetAddress("127.0.0.1", port);
  gfont = loadFont("EstrangeloEdessa-60.vlw");  // 事前に用意
  sfont = loadFont("EstrangeloEdessa-20.vlw");  // 事前に用意
  piano = loadImage("piano.png");               // 事前に用意
  background(150);
}

float midi2y(float midi) {
  return height - (midi - LOWER_BOUND + 1) * NOTE_HEIGHT;
}

int setCaveAt(int idx, int preLower, int preUpper) {
  center = constrain(random(center - MAX_DIFF, center + MAX_DIFF), 
              LOWER_BOUND + 2 * MIN_SPACE, UPPER_BOUND - 2 * MIN_SPACE);
  uppers[idx] = (int)constrain(
              random(preUpper - MAX_DIFF, preUpper + MAX_DIFF), 
              center + MIN_SPACE, UPPER_BOUND - MIN_SPACE);
  lowers[idx] = (int)constrain(
              random(preLower - MAX_DIFF + 1, preLower + MAX_DIFF + 1), 
              LOWER_BOUND + MIN_SPACE, center - MIN_SPACE);
  return (idx + 1) % NUM_NOTES;
}

void draw() {
  if (!isPlaying) return;
  dp = constrain(dp + GRAVITY, -MAX_DELTA, MAX_DELTA);
  pitch = constrain(pitch + dp, LOWER_BOUND, UPPER_BOUND);
  background(150);
  
  noStroke();
  int idx;
  for (int i = 0; i < NUM_NOTES; i++) {
    idx = (oldest + i) % NUM_NOTES;
    color uc = (idx == current ? color(255, 0, 0) : color(102, 102, 0));
    color lc = (idx == current ? color(255, 0, 0) : color(0, 102, 102));
    fill(uc);
    rect((i + 1) * NOTE_WIDTH - time, midi2y(uppers[idx]), 
         NOTE_WIDTH, NOTE_HEIGHT);
    fill(lc);
    rect((i + 1) * NOTE_WIDTH - time, midi2y(lowers[idx]), 
         NOTE_WIDTH, NOTE_HEIGHT);
  }
  
  image(piano, 0, 0);
  for (int i = 0; i < UPPER_BOUND - LOWER_BOUND + 1; i++) {
    stroke(125);
    line(piano.width, i * NOTE_HEIGHT, width, i * NOTE_HEIGHT);
  }

  if (isEasy) {
    noStroke();
    fill(color(255, 255, 0));
    ellipse(width / 2 + NOTE_WIDTH, midi2y(pitch), 4, 4);
  }
  
  fill(color(255, 255, 0));
  textAlign(RIGHT);
  textFont(sfont);
  text("Score: " + score, width - 30, 20);
  
  if (pitch > uppers[current] || pitch < lowers[current]) {
    fill(color(255, 255, 0));
    textAlign(CENTER);
    textFont(gfont);
    text("Game Over", width / 2, height / 2);
    isPlaying = false;
    noLoop();
  }
  
  time += dt;
  if (time > NOTE_WIDTH) {
    oldest = setCaveAt(oldest, 
               lowers[oldest - 1 < 0 ? NUM_NOTES - 1 : oldest - 1], 
               uppers[oldest - 1 < 0 ? NUM_NOTES - 1 : oldest - 1]);
    current = (current + 1) % NUM_NOTES;
    time = 0;
    if (score++ % 10 == 0) dt += SPEED_UP;
  }
  
  OscMessage msg = new OscMessage("/pitch");
  msg.add(isPlaying ? pitch : -1.0);
  osc.send(msg, addr);
}

void keyPressed() {
  if (!isPlaying && key == ENTER) {
    isPlaying = true;
    pitch = 60;
    dp = 0.5;
    time = 0;
    dt = 1.4;
    for (int i = 0; i < NUM_NOTES; i++) {
      if (i < NUM_NOTES * 2 / 3) {
        uppers[i] = UPPER_BOUND - MIN_SPACE;
        lowers[i] = LOWER_BOUND + MIN_SPACE;
        center = (UPPER_BOUND + LOWER_BOUND) / 2;
      } else {
        setCaveAt(i, lowers[i - 1], uppers[i - 1]);
      }
    }
    current = NUM_NOTES / 2;
    oldest = 0;
    score = 0;
    loop();
    OscMessage msg = new OscMessage("/pitch");
    msg.add(-1.0);
    osc.send(msg, addr);
  }
  if (key == CODED) {
    if (keyCode == UP) {
      dp = constrain(dp - 1.6 * GRAVITY, -MAX_DELTA, MAX_DELTA);
    }
  }
}

変数isEasyをtrueにするとイージーモードになる。