This commit is contained in:
FMS-Cat
2021-03-28 17:56:02 +09:00
parent 0ca17a9a39
commit ee7300b226
16 changed files with 514 additions and 343 deletions

View File

@@ -1,305 +0,0 @@
import { Channel } from '@fms-cat/automaton';
import { GLCat, GLCatBuffer, GLCatProgram, GLCatTexture, GLCatTransformFeedback } from '@fms-cat/glcat-ts';
import { MUSIC_AUTOMATON_TEXTURE_HEIGHT, MUSIC_BPM, MUSIC_BUFFER_LENGTH } from './config';
import { Pool } from './utils/Pool';
import { automaton } from './globals/automaton';
import { gl, glCat } from './globals/canvas';
import { injectCodeToShader } from './utils/injectCodeToShader';
import { randomTextureStatic } from './globals/randomTexture';
import musicVert from './shaders/music.vert';
import samplesOpus from './samples.opus';
import type { AutomatonWithGUI } from '@fms-cat/automaton-with-gui';
const discardFrag = '#version 300 es\nvoid main(){discard;}';
export class Music {
public isPlaying: boolean;
public time: number;
public deltaTime: number;
public audio: AudioContext;
private __program: GLCatProgram;
private __bufferOff: GLCatBuffer;
private __bufferTransformFeedbacks: [
GLCatBuffer,
GLCatBuffer
];
private __transformFeedback: GLCatTransformFeedback;
private __prevAudioTime: number;
private __bufferPool: Pool<AudioBuffer>;
private __prevBufferSource: AudioBufferSourceNode | null = null;
private __samples?: GLCatTexture;
private __automatonChannelList: Channel[];
private __automatonDefineString: string;
private __arrayAutomaton: Float32Array;
private __textureAutomaton: GLCatTexture;
public constructor( glCat: GLCat, audio: AudioContext ) {
this.audio = audio;
// == yoinked from wavenerd-deck ===============================================================
const bufferOffArray = new Array( MUSIC_BUFFER_LENGTH )
.fill( 0 )
.map( ( _, i ) => i );
this.__bufferOff = glCat.createBuffer();
this.__bufferOff.setVertexbuffer( new Float32Array( bufferOffArray ) );
this.__bufferTransformFeedbacks = [
glCat.createBuffer(),
glCat.createBuffer(),
];
this.__transformFeedback = glCat.createTransformFeedback();
this.__bufferTransformFeedbacks[ 0 ].setVertexbuffer(
MUSIC_BUFFER_LENGTH * 4,
gl.STREAM_READ
);
this.__bufferTransformFeedbacks[ 1 ].setVertexbuffer(
MUSIC_BUFFER_LENGTH * 4,
gl.STREAM_READ
);
this.__transformFeedback.bindBuffer( 0, this.__bufferTransformFeedbacks[ 0 ] );
this.__transformFeedback.bindBuffer( 1, this.__bufferTransformFeedbacks[ 1 ] );
// == automaton? ===============================================================================
this.__automatonChannelList = [];
this.__automatonDefineString = '';
this.__updateAutomatonChannelList();
this.__arrayAutomaton = new Float32Array(
MUSIC_BUFFER_LENGTH * MUSIC_AUTOMATON_TEXTURE_HEIGHT
);
this.__textureAutomaton = glCat.createTexture();
this.__textureAutomaton.textureFilter( gl.NEAREST );
// == program ==================================================================================
this.__program = glCat.lazyProgram(
injectCodeToShader( musicVert, this.__automatonDefineString ),
discardFrag,
{ transformFeedbackVaryings: [ 'outL', 'outR' ] },
);
// == audio ====================================================================================
this.__prevAudioTime = 0;
this.__bufferPool = new Pool( [
audio.createBuffer( 2, MUSIC_BUFFER_LENGTH, audio.sampleRate ),
audio.createBuffer( 2, MUSIC_BUFFER_LENGTH, audio.sampleRate ),
] );
this.__loadSamples();
// == I think these are gonna be required ======================================================
this.isPlaying = false;
this.time = 0.0;
this.deltaTime = 0.0;
// == hot hot hot hot hot ======================================================================
if ( process.env.DEV && module.hot ) {
const recompileShader = async (): Promise<void> => {
const program = await glCat.lazyProgramAsync(
injectCodeToShader( musicVert, this.__automatonDefineString ),
discardFrag,
{ transformFeedbackVaryings: [ 'outL', 'outR' ] },
).catch( ( error: any ) => {
console.error( error );
return null;
} );
if ( program ) {
this.__program.dispose( true );
this.__program = program;
}
};
module.hot.accept(
[ './shaders/music.vert' ],
() => {
recompileShader();
}
);
module.hot.accept(
[ './automaton.json' ],
() => {
this.__updateAutomatonChannelList();
recompileShader();
}
);
( automaton as AutomatonWithGUI ).on( 'createChannel', ( { name } ) => {
if ( name.startsWith( 'Music/' ) ) {
this.__updateAutomatonChannelList();
recompileShader();
}
} );
( automaton as AutomatonWithGUI ).on( 'removeChannel', ( { name } ) => {
if ( name.startsWith( 'Music/' ) ) {
this.__updateAutomatonChannelList();
recompileShader();
}
} );
}
}
public update(): void {
const { audio, isPlaying } = this;
const genTime = audio.currentTime;
if ( isPlaying ) {
this.deltaTime = genTime - this.__prevAudioTime;
this.time += this.deltaTime;
const buffer = this.__bufferPool.next();
if ( this.__program ) {
this.__updateAutomatonTexture();
this.__prepareBuffer( buffer );
}
const bufferSource = audio.createBufferSource();
bufferSource.buffer = buffer;
bufferSource.connect( audio.destination );
this.__prevBufferSource?.stop( audio.currentTime );
bufferSource.start( audio.currentTime, audio.currentTime - genTime );
this.__prevBufferSource = bufferSource;
} else {
this.deltaTime = 0.0;
this.__prevBufferSource?.stop( audio.currentTime );
this.__prevBufferSource = null;
}
this.__prevAudioTime = genTime;
}
private async __loadSamples(): Promise<void> {
const inputBuffer = await fetch( samplesOpus ).then( ( res ) => res.arrayBuffer() );
const audioBuffer = await this.audio.decodeAudioData( inputBuffer );
const buffer = new Float32Array( 96000 );
const data = audioBuffer.getChannelData( 0 );
for ( let i = 0; i < audioBuffer.length; i ++ ) {
buffer[ i ] = data[ i ];
}
const texture = glCat.createTexture()!;
texture.setTextureFromArray(
6000,
16,
buffer,
{
internalformat: gl.R32F,
format: gl.RED,
type: gl.FLOAT,
}
);
texture.textureFilter( gl.LINEAR );
this.__samples = texture;
}
private __updateAutomatonChannelList(): void {
this.__automatonChannelList = [];
this.__automatonDefineString = '';
for ( const [ channelName, channel ] of automaton.mapNameToChannel.entries() ) {
if ( channelName.startsWith( 'Music/' ) ) {
const key = channelName.substring( 6 );
const index = this.__automatonChannelList.length;
const y = ( index + 0.5 ) / MUSIC_AUTOMATON_TEXTURE_HEIGHT;
this.__automatonDefineString += `const float AUTO_${key}=${y};`;
this.__automatonChannelList.push( channel );
}
}
}
private __updateAutomatonTexture(): void {
for ( const [ iChannel, channel ] of this.__automatonChannelList.entries() ) {
for ( let iSample = 0; iSample < MUSIC_BUFFER_LENGTH; iSample ++ ) {
const t = this.time + iSample / this.audio.sampleRate;
this.__arrayAutomaton[ MUSIC_BUFFER_LENGTH * iChannel + iSample ] = channel.getValue( t );
}
}
this.__textureAutomaton.setTextureFromArray(
MUSIC_BUFFER_LENGTH,
MUSIC_AUTOMATON_TEXTURE_HEIGHT,
this.__arrayAutomaton,
{
internalformat: gl.R32F,
format: gl.RED,
type: gl.FLOAT,
}
);
}
private __prepareBuffer( buffer: AudioBuffer ): void {
const { time } = this;
const program = this.__program;
const beatLength = 60.0 / MUSIC_BPM;
const barLength = 240.0 / MUSIC_BPM;
const sixteenBarLength = 3840.0 / MUSIC_BPM;
program.attribute( 'off', this.__bufferOff, 1 );
program.uniform( 'bpm', '1f', MUSIC_BPM );
program.uniform( 'bufferLength', '1f', MUSIC_BUFFER_LENGTH );
program.uniform( '_deltaSample', '1f', 1.0 / this.audio.sampleRate );
program.uniform(
'timeLength',
'4f',
beatLength,
barLength,
sixteenBarLength,
1E16
);
program.uniform(
'_timeHead',
'4f',
time % beatLength,
time % barLength,
time % sixteenBarLength,
time,
);
program.uniformTexture( 'samplerRandom', randomTextureStatic.texture );
program.uniformTexture( 'samplerAutomaton', this.__textureAutomaton );
if ( this.__samples ) {
program.uniformTexture( 'samplerSamples', this.__samples );
}
glCat.useProgram( program, () => {
glCat.bindTransformFeedback( this.__transformFeedback, () => {
gl.enable( gl.RASTERIZER_DISCARD );
gl.beginTransformFeedback( gl.POINTS );
gl.drawArrays( gl.POINTS, 0, MUSIC_BUFFER_LENGTH );
gl.endTransformFeedback();
gl.disable( gl.RASTERIZER_DISCARD );
} );
} );
gl.flush();
[ 0, 1 ].map( ( i ) => {
glCat.bindVertexBuffer( this.__bufferTransformFeedbacks[ i ], () => {
gl.getBufferSubData(
gl.ARRAY_BUFFER,
0,
buffer.getChannelData( i ),
0,
MUSIC_BUFFER_LENGTH
);
} );
} );
}
}

File diff suppressed because one or more lines are too long

View File

@@ -6,5 +6,7 @@ export const
RESOLUTION = [ 1280, 720 ],
MUSIC_BPM = 180,
START_POSITION = 36,
MUSIC_BUFFER_LENGTH = 4096,
MUSIC_AUTOMATON_TEXTURE_HEIGHT = 16;
MUSIC_LENGTH = 213,
MUSIC_AUTOMATON_TEXTURE_HEIGHT = 16,
IBLLUT_ITER = 400,
IBLLUT_SIZE = 256;

View File

@@ -1,6 +1,7 @@
import { BufferRenderTarget } from '../heck/BufferRenderTarget';
import { Entity } from '../heck/Entity';
import { GLCatTexture } from '@fms-cat/glcat-ts';
import { IBLLUT_ITER, IBLLUT_SIZE } from '../config';
import { Lambda } from '../heck/components/Lambda';
import { Material } from '../heck/Material';
import { Quad } from '../heck/components/Quad';
@@ -11,8 +12,6 @@ import { vdc } from '../utils/vdc';
import iblLutFrag from '../shaders/ibl-lut.frag';
import quadVert from '../shaders/quad.vert';
const IBL_SIZE = 256;
export class IBLLUT {
public entity: Entity;
@@ -29,13 +28,13 @@ export class IBLLUT {
// -- swap -------------------------------------------------------------------------------------
this.swap = new Swap(
new BufferRenderTarget( {
width: IBL_SIZE,
height: IBL_SIZE,
width: IBLLUT_SIZE,
height: IBLLUT_SIZE,
name: process.env.DEV && 'IBLLUT/swap0',
} ),
new BufferRenderTarget( {
width: IBL_SIZE,
height: IBL_SIZE,
width: IBLLUT_SIZE,
height: IBLLUT_SIZE,
name: process.env.DEV && 'IBLLUT/swap1',
} ),
);
@@ -64,7 +63,7 @@ export class IBLLUT {
samples ++;
this.swap.swap();
if ( samples > 1024 ) {
if ( samples > IBLLUT_ITER ) {
this.entity.active = false;
} else {
material.addUniform( 'samples', '1f', samples );

View File

@@ -63,12 +63,6 @@ export class LightEntity extends Entity {
this.camera.clear = [ 1.0, 1.0, 1.0, 1.0 ];
this.components.push( this.camera );
this.shadowMap = new BufferRenderTarget( {
width: options.shadowMapWidth ?? 1024,
height: options.shadowMapHeight ?? 1024,
name: process.env.DEV && `${ options.namePrefix }/shadowMap`,
} );
swap.swap();
// -- blur -------------------------------------------------------------------------------------
@@ -82,12 +76,15 @@ export class LightEntity extends Entity {
material.addUniformTexture( 'sampler0', swap.i.texture );
this.components.push( new Quad( {
target: i === 0 ? swap.o : this.shadowMap,
target: swap.o,
material,
name: process.env.DEV && `${ options.namePrefix }/quadShadowBlur${ i }`,
} ) );
swap.swap();
}
// -- this is the shadow map -------------------------------------------------------------------
this.shadowMap = swap.i;
}
}

View File

@@ -1,9 +1,9 @@
import { Automaton } from '@fms-cat/automaton';
import { AutomatonWithGUI } from '@fms-cat/automaton-with-gui';
import { Music } from '../music/Music';
import { fxDefinitions } from '../automaton-fxs/fxDefinitions';
import { getDivAutomaton } from './dom';
import automatonData from '../automaton.json';
import type { Music } from '../Music';
export const automaton = ( () => {
if ( process.env.DEV ) {
@@ -13,7 +13,7 @@ export const automaton = ( () => {
automatonData,
{
gui: getDivAutomaton(),
isPlaying: true,
isPlaying: false,
fxDefinitions,
},
);

View File

@@ -1,7 +1,9 @@
import { Music } from '../Music';
import { Music } from '../music/Music';
import { MusicOffline } from '../music/MusicOffline';
import { MusicRealtime } from '../music/MusicRealtime';
import { automatonSetupMusic } from './automaton';
import { glCat } from './canvas';
export const audio = new AudioContext();
export const music = new Music( glCat, audio );
// export const music: Music = new MusicRealtime();
export const music: Music = new MusicOffline();
automatonSetupMusic( music );

View File

@@ -23,26 +23,12 @@ if ( process.env.DEV ) {
canvas.style.margin = 'auto';
canvas.style.maxWidth = '100%';
canvas.style.maxHeight = '100%';
music.isPlaying = true;
music.time = START_POSITION;
} else {
canvas.style.position = 'fixed';
canvas.style.left = '0';
canvas.style.top = '0';
document.body.style.width = canvas.style.width = '100%';
document.body.style.height = canvas.style.height = '100%';
const button = document.createElement( 'a' );
document.body.appendChild( button );
button.innerHTML = 'click me!';
button.onclick = () => {
document.body.appendChild( canvas );
music.isPlaying = true;
music.time = START_POSITION;
document.body.requestFullscreen();
};
}
// -- keyboards ------------------------------------------------------------------------------------
@@ -85,3 +71,24 @@ if ( process.env.DEV ) {
console.info( Component.nameMap );
console.info( BufferRenderTarget.nameMap );
}
// -- let's gooooo ---------------------------------------------------------------------------------
async function load(): Promise<void> {
await music.prepare();
if ( process.env.DEV ) {
music.time = START_POSITION;
( automaton as AutomatonWithGUI ).play();
} else {
const button = document.createElement( 'a' );
document.body.appendChild( button );
button.innerHTML = 'click me!';
button.onclick = () => {
document.body.appendChild( canvas );
music.isPlaying = true;
document.body.requestFullscreen();
};
}
}
load();

View File

@@ -0,0 +1,99 @@
import { Channel } from '@fms-cat/automaton';
import { GLCatTexture } from '@fms-cat/glcat-ts';
import { MUSIC_AUTOMATON_TEXTURE_HEIGHT } from '../config';
import { Music } from './Music';
import { audio } from '../globals/music';
import { automaton } from '../globals/automaton';
import { gl, glCat } from '../globals/canvas';
import type { AutomatonWithGUI } from '@fms-cat/automaton-with-gui';
/**
* Inject automaton params into music...
*/
export class AutomatonManager {
public texture: GLCatTexture;
public defineString: string;
private __music: Music;
private __ChannelList: Channel[];
private __array: Float32Array;
public constructor( music: Music ) {
this.__music = music;
this.defineString = '';
this.__ChannelList = [];
this.__updateAutomatonChannelList();
this.__array = new Float32Array(
music.bufferLength * MUSIC_AUTOMATON_TEXTURE_HEIGHT
);
this.texture = glCat.createTexture();
this.texture.textureFilter( gl.NEAREST );
// == hot hot hot hot hot ======================================================================
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept(
[ '../automaton.json' ],
() => {
this.__updateAutomatonChannelList();
music.recompile?.();
}
);
}
( automaton as AutomatonWithGUI ).on( 'createChannel', ( { name } ) => {
if ( name.startsWith( 'Music/' ) ) {
this.__updateAutomatonChannelList();
music.recompile?.();
}
} );
( automaton as AutomatonWithGUI ).on( 'removeChannel', ( { name } ) => {
if ( name.startsWith( 'Music/' ) ) {
this.__updateAutomatonChannelList();
music.recompile?.();
}
} );
}
}
public update( time: number ): void {
const bufferLength = this.__music.bufferLength;
for ( const [ iChannel, channel ] of this.__ChannelList.entries() ) {
for ( let iSample = 0; iSample < bufferLength; iSample ++ ) {
const t = time + iSample / audio.sampleRate;
this.__array[ bufferLength * iChannel + iSample ] = channel.getValue( t );
}
}
this.texture.setTextureFromArray(
bufferLength,
MUSIC_AUTOMATON_TEXTURE_HEIGHT,
this.__array,
{
internalformat: gl.R32F,
format: gl.RED,
type: gl.FLOAT,
}
);
}
private __updateAutomatonChannelList(): void {
this.__ChannelList = [];
this.defineString = '';
for ( const [ channelName, channel ] of automaton.mapNameToChannel.entries() ) {
if ( channelName.startsWith( 'Music/' ) ) {
const key = channelName.substring( 6 );
const index = this.__ChannelList.length;
const y = ( index + 0.5 ) / MUSIC_AUTOMATON_TEXTURE_HEIGHT;
this.defineString += `const float AUTO_${key}=${y};`;
this.__ChannelList.push( channel );
}
}
}
}

178
src/music/Music.ts Normal file
View File

@@ -0,0 +1,178 @@
import { AutomatonManager } from './AutomatonManager';
import { GLCatBuffer, GLCatProgram, GLCatTransformFeedback } from '@fms-cat/glcat-ts';
import { MUSIC_BPM } from '../config';
import { SamplesManager } from './SamplesManager';
import { audio } from '../globals/music';
import { gl, glCat } from '../globals/canvas';
import { injectCodeToShader } from '../utils/injectCodeToShader';
import { randomTextureStatic } from '../globals/randomTexture';
import musicVert from './music.vert';
const discardFrag = '#version 300 es\nvoid main(){discard;}';
export abstract class Music {
public isPlaying: boolean;
public time: number;
public deltaTime: number;
public readonly bufferLength: number;
protected __program: GLCatProgram;
protected __samplesManager: SamplesManager;
protected __automatonManager: AutomatonManager;
private __bufferOff: GLCatBuffer;
private __bufferTransformFeedbacks: [
GLCatBuffer,
GLCatBuffer,
];
private __transformFeedback: GLCatTransformFeedback;
private __prevAudioTime: number;
public constructor( bufferLength: number ) {
this.isPlaying = false;
this.time = 0.0;
this.deltaTime = 0.0;
this.__prevAudioTime = 0.0;
this.bufferLength = bufferLength;
// == spicy boys ===============================================================================
this.__samplesManager = new SamplesManager( this );
this.__automatonManager = new AutomatonManager( this );
// == gl =======================================================================================
const bufferOffArray = new Array( bufferLength )
.fill( 0 )
.map( ( _, i ) => i );
this.__bufferOff = glCat.createBuffer();
this.__bufferOff.setVertexbuffer( new Float32Array( bufferOffArray ) );
this.__bufferTransformFeedbacks = [
glCat.createBuffer(),
glCat.createBuffer(),
];
this.__transformFeedback = glCat.createTransformFeedback();
this.__bufferTransformFeedbacks[ 0 ].setVertexbuffer(
bufferLength * 4,
gl.STREAM_READ
);
this.__bufferTransformFeedbacks[ 1 ].setVertexbuffer(
bufferLength * 4,
gl.STREAM_READ
);
this.__transformFeedback.bindBuffer( 0, this.__bufferTransformFeedbacks[ 0 ] );
this.__transformFeedback.bindBuffer( 1, this.__bufferTransformFeedbacks[ 1 ] );
this.__program = glCat.lazyProgram(
injectCodeToShader( musicVert, this.__automatonManager.defineString ),
discardFrag,
{ transformFeedbackVaryings: [ 'outL', 'outR' ] },
);
// == hot hot hot hot hot ======================================================================
if ( process.env.DEV && module.hot ) {
module.hot.accept(
[ './music.vert' ],
() => {
this.recompile();
}
);
}
}
public async prepare(): Promise<void> {
await this.__samplesManager.loadSamples();
}
public async recompile(): Promise<void> {
if ( process.env.DEV ) {
const program = await glCat.lazyProgramAsync(
injectCodeToShader( musicVert, this.__automatonManager.defineString ),
discardFrag,
{ transformFeedbackVaryings: [ 'outL', 'outR' ] },
).catch( ( error: any ) => {
console.error( error );
return null;
} );
if ( program ) {
this.__program.dispose( true );
this.__program = program;
}
}
}
public update(): void {
const now = audio.currentTime;
if ( this.isPlaying ) {
this.deltaTime = now - this.__prevAudioTime;
this.time += this.deltaTime;
}
this.__updateImpl();
this.__prevAudioTime = now;
}
protected abstract __updateImpl(): void;
protected __render( time: number, callback: ( channel: number ) => void ): void {
this.__automatonManager.update( time );
const program = this.__program;
const beatLength = 60.0 / MUSIC_BPM;
const barLength = 240.0 / MUSIC_BPM;
const sixteenBarLength = 3840.0 / MUSIC_BPM;
program.attribute( 'off', this.__bufferOff, 1 );
program.uniform( 'bpm', '1f', MUSIC_BPM );
program.uniform( 'bufferLength', '1f', this.bufferLength );
program.uniform( '_deltaSample', '1f', 1.0 / audio.sampleRate );
program.uniform(
'timeLength',
'4f',
beatLength,
barLength,
sixteenBarLength,
1E16
);
program.uniform(
'_timeHead',
'4f',
time % beatLength,
time % barLength,
time % sixteenBarLength,
time,
);
program.uniformTexture( 'samplerRandom', randomTextureStatic.texture );
program.uniformTexture( 'samplerAutomaton', this.__automatonManager.texture );
if ( this.__samplesManager.texture ) {
program.uniformTexture( 'samplerSamples', this.__samplesManager.texture );
}
glCat.useProgram( program, () => {
glCat.bindTransformFeedback( this.__transformFeedback, () => {
gl.enable( gl.RASTERIZER_DISCARD );
gl.beginTransformFeedback( gl.POINTS );
gl.drawArrays( gl.POINTS, 0, this.bufferLength );
gl.endTransformFeedback();
gl.disable( gl.RASTERIZER_DISCARD );
} );
} );
gl.finish(); // fenceよくわからん
[ 0, 1 ].map( ( i ) => {
glCat.bindVertexBuffer(
this.__bufferTransformFeedbacks[ i ],
() => callback( i ),
);
} );
}
}

67
src/music/MusicOffline.ts Normal file
View File

@@ -0,0 +1,67 @@
import { MUSIC_LENGTH } from '../config';
import { Music } from './Music';
import { audio } from '../globals/music';
import { gl } from '../globals/canvas';
const BUFFER_LENGTH = 16384;
export class MusicOffline extends Music {
private __buffer: AudioBuffer;
private __currentBufferSource?: AudioBufferSourceNode | null;
public constructor() {
super( BUFFER_LENGTH );
this.__buffer = audio.createBuffer(
2,
MUSIC_LENGTH * audio.sampleRate,
audio.sampleRate
);
}
public async prepare(): Promise<void> {
await super.prepare();
let head = 0;
return new Promise( ( resolve ) => {
const render = (): void => {
const remain = ( MUSIC_LENGTH * audio.sampleRate ) - head;
if ( remain <= 0 ) {
resolve();
return;
}
this.__render( head / audio.sampleRate, ( i ) => {
gl.getBufferSubData(
gl.ARRAY_BUFFER,
0,
this.__buffer.getChannelData( i ),
head,
Math.min( remain, BUFFER_LENGTH )
);
} );
head += BUFFER_LENGTH;
setTimeout( render, 1 );
};
render();
} );
}
public __updateImpl(): void {
if ( this.isPlaying && this.__currentBufferSource == null ) {
this.__currentBufferSource = audio.createBufferSource();
this.__currentBufferSource.buffer = this.__buffer;
this.__currentBufferSource.connect( audio.destination );
this.__currentBufferSource.start( audio.currentTime, this.time );
}
if ( !this.isPlaying && this.__currentBufferSource != null ) {
this.__currentBufferSource.stop( audio.currentTime );
this.__currentBufferSource = null;
}
}
}

View File

@@ -0,0 +1,55 @@
import { Music } from './Music';
import { Pool } from '../utils/Pool';
import { audio } from '../globals/music';
import { gl } from '../globals/canvas';
const BUFFER_LENGTH = 4096;
export class MusicRealtime extends Music {
private __bufferPool: Pool<AudioBuffer>;
private __prevBufferSource: AudioBufferSourceNode | null = null;
public constructor() {
super( BUFFER_LENGTH );
this.__bufferPool = new Pool( [
audio.createBuffer( 2, BUFFER_LENGTH, audio.sampleRate ),
audio.createBuffer( 2, BUFFER_LENGTH, audio.sampleRate ),
] );
// casually calling async function
this.__samplesManager.loadSamples();
}
public __updateImpl(): void {
if ( this.isPlaying ) {
const buffer = this.__bufferPool.next();
const genTime = audio.currentTime;
if ( this.__program ) {
this.__render( this.time, ( i ) => {
gl.getBufferSubData(
gl.ARRAY_BUFFER,
0,
buffer.getChannelData( i ),
0,
BUFFER_LENGTH
);
} );
}
const bufferSource = audio.createBufferSource();
bufferSource.buffer = buffer;
bufferSource.connect( audio.destination );
this.__prevBufferSource?.stop( audio.currentTime );
bufferSource.start( audio.currentTime, audio.currentTime - genTime );
this.__prevBufferSource = bufferSource;
} else {
this.deltaTime = 0.0;
this.__prevBufferSource?.stop( audio.currentTime );
this.__prevBufferSource = null;
}
}
}

View File

@@ -0,0 +1,62 @@
import { GLCatTexture } from '@fms-cat/glcat-ts';
import { Music } from './Music';
import { audio } from '../globals/music';
import { gl, glCat } from '../globals/canvas';
import samplesOpus from './samples.opus';
export class SamplesManager {
public __music: Music;
/**
* it can be undefined since it's loaded asynchronously
*/
public texture?: GLCatTexture;
public constructor( music: Music ) {
this.__music = music;
// == hot hot hot hot hot ======================================================================
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept(
[ './samples.opus' ],
async () => {
await this.loadSamples();
music.recompile?.();
}
);
}
}
}
public async loadSamples(): Promise<void> {
const inputBuffer = await fetch( samplesOpus ).then( ( res ) => res.arrayBuffer() );
const audioBuffer = await audio.decodeAudioData( inputBuffer );
const buffer = new Float32Array( 96000 );
const data = audioBuffer.getChannelData( 0 );
for ( let i = 0; i < audioBuffer.length; i ++ ) {
buffer[ i ] = data[ i ];
}
const texture = glCat.createTexture()!;
texture.setTextureFromArray(
6000,
16,
buffer,
{
internalformat: gl.R32F,
format: gl.RED,
type: gl.FLOAT,
}
);
texture.textureFilter( gl.LINEAR );
if ( process.env.DEV && this.texture != null ) {
this.texture.dispose();
}
this.texture = texture;
}
}

View File

@@ -14,6 +14,7 @@ import { FlashyTerrain } from './entities/FlashyTerrain';
import { FlickyParticles } from './entities/FlickyParticles';
import { Glitch } from './entities/Glitch';
import { IBLLUT } from './entities/IBLLUT';
import { IFSPistons } from './entities/IFSPistons';
import { Lambda } from './heck/components/Lambda';
import { LightEntity } from './entities/LightEntity';
import { PixelSorter } from './entities/PixelSorter';
@@ -162,6 +163,13 @@ if ( process.env.DEV && module.hot ) {
} );
}
const replacerIFSPistons = new EntityReplacer( () => new IFSPistons(), 'IFSPistons' );
if ( process.env.DEV && module.hot ) {
module.hot.accept( './entities/IFSPistons', () => {
replacerIFSPistons.replace();
} );
}
// -- things that is not an "object" ---------------------------------------------------------------
const swapOptions = {
width: canvasRenderTarget.width,
@@ -187,7 +195,7 @@ const replacerLightFirst = new EntityReplacer( () => {
shadowMapFar: 20.0,
namePrefix: process.env.DEV && 'lightFirst',
} );
light.color = [ 1.0, 1.0, 1.0 ];
light.color = [ 40.0, 40.0, 40.0 ];
light.transform.lookAt( new Vector3( [ -1.0, 2.0, 8.0 ] ) );
return light;
}, 'LightFirst' );
@@ -201,7 +209,7 @@ const replacerLightPink = new EntityReplacer( () => {
shadowMapFar: 20.0,
namePrefix: process.env.DEV && 'lightPink',
} );
light.color = [ 60.0, 1.0, 5.0 ];
light.color = [ 120.0, 2.0, 10.0 ];
light.transform.lookAt( new Vector3( [ -1.0, 4.0, 4.0 ] ) );
return light;
}, 'LightPink' );