AYM·JS
UN SYNTHÉTISEUR CHIPTUNE
DANS VOTRE NAVIGATEUR

À LA DÉCOUVERTE DES API Web Audio 🎧 ET Web MIDI 🎹

POURQUOI CE PROJET ?

THAT'S THE QUESTION

EN QUELQUES MOTS

arcade

  • 🎶 Retrouver le son d'antan
  • 🕹️ Préserver le capital historique
  • 🤓 Découvrir les API Web Audio et Web MIDI
  • 🥳 C'est Fun

LE SON CHIPTUNE

THAT 80'S SHOW

LE SON CHIPTUNE

arcade

  • La musique de la génération des 8 bits
  • Coloration sonore caractéristique
  • Synthèse sonore limitée
  • Polyphonie restreinte

L'API Web Audio

BECAUSE SILENCE IS OVERRATED

L'API Web Audio

speaker

  • Premier draft du W3C en 2011
  • Promu en tant que standard le 17 juin 2021
  • Permet l'audio en temps réel dans le navigateur
  • Modèle basé sur un système de graphe nodal
  • Garantit des performances élevées
  • Garantit une faible latence

UN SYSTÈME DE GRAPHE NODAL

speaker

modular-routing0

modular-routing1

modular-routing2

modular-routing3

OSCILLATOR

GÉNÉRER DES SIGNAUX PÉRIODIQUES

OSCILLATOR

GÉNÉRER DES SIGNAUX PÉRIODIQUES

  • Signaux sinusoïdaux
  • Signaux carrés
  • Signaux dents de scies
  • Signaux triangulaires
  • Signaux personnalisés

signals

OSCILLATOR

UN EXEMPLE SIMPLE

example01

UN EXEMPLE SIMPLE

DÉFINITION DE LA CLASSE


export class WebAudioExample {
    constructor() {
        /* code */
    }

    play(type) {
        /* code */
    }

    stop() {
        /* code */
    }

    set_freq(value) {
        /* code */
    }

    set_gain(value) {
        /* code */
    }
}
                            

UN EXEMPLE SIMPLE

LE CONSTRUCTEUR


export class WebAudioExample {
    constructor() {
        this.waContext    = null;
        this.waGain       = null;
        this.waOscillator = null;
    }

    play(type) {
        /* code */
    }

    stop() {
        /* code */
    }

    set_freq(value) {
        /* code */
    }

    set_gain(value) {
        /* code */
    }
}
                            

UN EXEMPLE SIMPLE

LA MÉTHODE play()


export class WebAudioExample {
    constructor() {
        /* code */
    }

    play(type) {
        if(this.waContext == null) {
            this.waContext = new AudioContext();
        }
        if(this.waGain == null) {
            this.waGain = new GainNode(this.waContext);
            this.waGain.connect(this.waContext.destination);
        }
        if(this.waOscillator == null) {
            this.waOscillator = new OscillatorNode(this.waContext, {
                type: type
            });
            this.waOscillator.connect(this.waGain);
            this.waOscillator.start();
        }
        else {
            this.waOscillator.type = type;
        }
    }

    stop() {
        /* code */
    }

    set_freq(value) {
        /* code */
    }

    set_gain(value) {
        /* code */
    }
}
                            

UN EXEMPLE SIMPLE

LA MÉTHODE stop()


export class WebAudioExample {
    constructor() {
        /* code */
    }

    play(type) {
        /* code */
    }

    stop() {
        if(this.waOscillator != null) {
            this.waOscillator.stop();
            this.waOscillator.disconnect();
            this.waOscillator = null;
        }
    }

    set_freq(value) {
        /* code */
    }

    set_gain(value) {
        /* code */
    }
}
                            

UN EXEMPLE SIMPLE

LA MÉTHODE set_freq()


export class WebAudioExample {
    constructor() {
        /* code */
    }

    play(type) {
        /* code */
    }

    stop() {
        /* code */
    }

    set_freq(value) {
        if(this.waOscillator != null) {
            this.waOscillator.frequency.setValueAtTime(value, this.waContext.currentTime);
        }
    }

    set_gain(value) {
        /* code */
    }
}
                            

UN EXEMPLE SIMPLE

LA MÉTHODE set_gain()


export class WebAudioExample {
    constructor() {
        /* code */
    }

    play(type) {
        /* code */
    }

    stop() {
        /* code */
    }

    set_freq(value) {
        /* code */
    }

    set_gain(value) {
        if(this.waGain != null) {
            this.waGain.gain.value = (value / 100.0);
        }
    }
}
                            

UN EXEMPLE SIMPLE

PLACE AU TEST

example01-play

AUDIO WORKLET PROCESSOR

GÉNÉRER ET MANIPULER DES SAMPLES

AUDIO WORKLET PROCESSOR

GÉNÉRER ET MANIPULER DES SAMPLES

  • Fonctionne dans un Worklet (thread) séparé
  • Communique à l'aide d'une file de messages
  • Permet de lire et produire des samples
  • Permet d'appliquer des effets
  • Etc...

AUDIO WORKLET PROCESSOR

STRUCTURE GLOBALE


export class MyAudioWorkletProcessor extends AudioWorkletProcessor {
    constructor(options) {
        super();
        /* some setup */
    }

    process(inputs, outputs, parameters) {
        /* some processing */
        return true;
    }
}

registerProcessor("my-audio-worklet-processor", MyAudioWorkletProcessor);
                            

AUDIO WORKLET PROCESSOR

UTILISATION


async function initialize() {
    let context = new AudioContext();

    await context.audioWorklet.addModule('my-audio-worklet-processor.js');

    let worklet = new AudioWorkletNode(context, 'my-audio-worklet-processor', {
        numberOfInputs: 0,
        numberOfOutputs: 1,
        outputChannelCount: [2],
    });

    worklet.connect(context.destination);
}
                            

AUDIO WORKLET PROCESSOR

UN EXEMPLE SIMPLE

example02

UN EXEMPLE SIMPLE

DÉFINITION DE LA CLASSE


export class WebAudioExample {
    constructor() {
        /* code */
    }

    async play(freq) {
        /* code */
    }

    async stop() {
        /* code */
    }

    async set_freq(value) {
        /* code */
    }

    async set_gain(value) {
        /* code */
    }
}
                            

UN EXEMPLE SIMPLE

LE CONSTRUCTEUR


export class WebAudioExample {
    constructor() {
        this.waContext = null;
        this.waGain    = null;
        this.waWorklet = null;
    }

    async play(freq) {
        /* code */
    }

    async stop() {
        /* code */
    }

    async set_freq(value) {
        /* code */
    }

    async set_gain(value) {
        /* code */
    }
}
                            

UN EXEMPLE SIMPLE

LA MÉTHODE play()


export class WebAudioExample {
    constructor() {
        /* code */
    }

    async play(freq) {
        if(this.waContext == null) {
            this.waContext = new AudioContext();
            await this.waContext.audioWorklet.addModule('audio-worklet-processor-example.js');
        }
        if(this.waGain == null) {
            this.waGain = new GainNode(this.waContext);
            this.waGain.connect(this.waContext.destination);
        }
        if(this.waWorklet == null) {
            this.waWorklet = new AudioWorkletNode(this.waContext, 'audio-worklet-processor-example', {
                numberOfInputs: 0,
                numberOfOutputs: 1,
                outputChannelCount: [2]
            });
            this.waWorklet.connect(this.waGain);
        }
        if(this.waWorklet != null) {
            this.waWorklet.port.postMessage({ type: 'Play', data: freq });
        }
    }

    async stop() {
        /* code */
    }

    async set_freq(value) {
        /* code */
    }

    async set_gain(value) {
        /* code */
    }
}
                            

UN EXEMPLE SIMPLE

LA MÉTHODE stop()


export class WebAudioExample {
    constructor() {
        /* code */
    }

    async play(freq) {
        /* code */
    }

    async stop() {
        if(this.waWorklet != null) {
            this.waWorklet.port.postMessage({ type: 'Stop', data: 0 });
        }
    }

    async set_freq(value) {
        /* code */
    }

    async set_gain(value) {
        /* code */
    }
}
                            

UN EXEMPLE SIMPLE

LA MÉTHODE set_freq()


export class WebAudioExample {
    constructor() {
        /* code */
    }

    async play(freq) {
        /* code */
    }

    async stop() {
        /* code */
    }

    async set_freq(value) {
        if(this.waWorklet != null) {
            this.waWorklet.port.postMessage({ type: 'Freq', data: value });
        }
    }

    async set_gain(value) {
        /* code */
    }
}
                            

UN EXEMPLE SIMPLE

LA MÉTHODE set_gain()


export class WebAudioExample {
    constructor() {
        /* code */
    }

    async play(freq) {
        /* code */
    }

    async stop() {
        /* code */
    }

    async set_freq(value) {
        /* code */
    }

    async set_gain(value) {
        if(this.waGain != null) {
            this.waGain.gain.value = (value / 100.0);
        }
    }
}
                            

UN EXEMPLE SIMPLE

LE PROCESSEUR


export class AudioWorkletProcessorExample extends AudioWorkletProcessor {
    constructor(options) {
        /* code */
    }

    process(inputs, outputs, parameters) {
        /* code */
    }
}

registerProcessor("audio-worklet-processor-example", AudioWorkletProcessorExample);
                            

UN EXEMPLE SIMPLE

LE CONSTRUCTEUR


export class AudioWorkletProcessorExample extends AudioWorkletProcessor {
    constructor(options) {
        super();
        this.freq  = 0;
        this.count = 0;
        this.value = 0;
        this.port.onmessage = (message) => {
            const event = message.data;
            switch(event.type) {
                case 'Play':
                    this.freq  = event.data;
                    this.value = 1;
                    break;
                case 'Stop':
                    this.freq  = 0;
                    this.value = 0;
                    break;
                case 'Freq':
                    this.freq  = event.data;
                    break;
                default:
                    break;
            }
        };
    }

    process(inputs, outputs, parameters) {
        /* code */
    }
}

registerProcessor("audio-worklet-processor-example", AudioWorkletProcessorExample);
                            

UN EXEMPLE SIMPLE

LA MÉTHODE process()


export class AudioWorkletProcessorExample extends AudioWorkletProcessor {
    constructor(options) {
        /* code */
    }

    process(inputs, outputs, parameters) {
        const sampleFreq  = this.freq * 2;
        const sampleCount = outputs[0][0].length;
        for(let sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) {
            this.count += sampleFreq;
            if(this.count >= sampleRate) {
                this.count -= sampleRate;
                this.value = -this.value;
            }
            for(const output of outputs) {
                for(const channel of output) {
                    channel[sampleIndex] = this.value;
                }
            }
        }
        return true;
    }
}

registerProcessor("audio-worklet-processor-example", AudioWorkletProcessorExample);
                            

UN EXEMPLE SIMPLE

DÉCLARATION DU PROCESSEUR


export class AudioWorkletProcessorExample extends AudioWorkletProcessor {
    constructor(options) {
        /* code */
    }

    process(inputs, outputs, parameters) {
        /* code */
    }
}

registerProcessor("audio-worklet-processor-example", AudioWorkletProcessorExample);
                            

AUDIO WORKLET PROCESSOR

PLACE AU TEST

example02-play

AYM-JS

UN ÉMULATEUR DE AY-3-8910 EN JAVASCRIPT

GI · AY-3-8910

PROGRAMMABLE SOUND GENERATOR

  • Conçu par General Instrument en 1978
  • Générateur de signaux carrés
  • Fréquences de 30Hz à 125Khz
  • 3 voies indépendantes
  • 1 générateur de bruit blanc
  • 1 générateur d'enveloppe
  • 1 unité de mixage

GI · AY-3-8910

TRÈS UTILISÉ DANS LES ANNÉES 80

  • Bornes d'Arcardes (1942, Bombjack, ...)
  • Filppers (Flash Gordon, Xenon, ...)
  • La gamme Amstrad CPC et GX4000
  • La gamme Atari ST
  • La gamme MSX
  • Etc...

GI · AY-3-8910

LES ENTRAILLES DU CHIP

AY-3-8910-dil

AY-3-8910-die

AY-3-8910-block-diagram-1

AY-3-8910-block-diagram-2

AY-3-8910-registers-1

AY-3-8910-registers-2

AY-3-8910-envelope-1

AY-3-8910-envelope-2

L'ÉMULATEUR DE AY-3-8910

INTERNAL STATE


class AYM_State {
    constructor() {
        this.index = 0;
        this.array = new Uint8Array(16);
    }

    reset() {
        this.index &= 0;
        const count = this.array.length;
        for(let index = 0; index < count; ++index) {
            this.array[index] &= 0;
        }
    }
}
                            

L'ÉMULATEUR DE AY-3-8910

TONE GENERATOR


class AYM_ToneGenerator {
    constructor() {
        this.counter = 0;
        this.period  = 0;
        this.phase   = 0;
    }

    reset() {
        this.counter &= 0;
        this.period  &= 0;
        this.phase   &= 0;
    }

    clock() {
        if(++this.counter >= this.period) {
            this.counter &= 0;
            this.phase   ^= 1;
        }
    }

    set_coarse_tune(value) {
        const msb = ((value & 0xff) << 8);
        const lsb = (this.period & (0xff << 0));
        this.period = (msb | lsb);
    }

    set_fine_tune(value) {
        const msb = (this.period & (0xff << 8));
        const lsb = ((value & 0xff) << 0);
        this.period = (msb | lsb);
    }
}
                            

L'ÉMULATEUR DE AY-3-8910

NOISE GENERATOR


class AYM_NoiseGenerator {
    constructor() {
        this.counter = 0;
        this.period  = 0;
        this.phase   = 0;
    }

    reset() {
        this.counter &= 0;
        this.period  &= 0;
        this.phase   &= 0;
    }

    clock() {
        if(++this.counter >= this.period) {
            this.counter &= 0;
            const lfsr = this.phase;
            const bit0 = (lfsr << 16);
            const bit3 = (lfsr << 13);
            const msb  = (~(bit0 ^ bit3) & 0x10000);
            const lsb  = ((lfsr >> 1) & 0x0ffff);
            this.phase = (msb | lsb);
        }
    }

    set_coarse_tune(value) {
        const msb = ((value & 0xff) << 9);
        const lsb = (this.period & (0xff << 1));
        this.period = (msb | lsb);
    }

    set_fine_tune(value) {
        const msb = (this.period & (0xff << 9));
        const lsb = ((value & 0xff) << 1);
        this.period = (msb | lsb);
    }
}
                            

L'ÉMULATEUR DE AY-3-8910

ENVELOPE GENERATOR


class AYM_EnvelopeGenerator {
    constructor() {
        this.counter = 0;
        this.period  = 0;
        this.shape   = 0;
        this.phase   = 0;
        this.level   = 0;

        const ramp_up = () => {
            this.level = ((this.level + 1) & 0x1f);
            if(this.level == 0x1f) {
                this.phase ^= 1;
            }
        };

        const ramp_down = () => {
            this.level = ((this.level - 1) & 0x1f);
            if(this.level == 0x00) {
                this.phase ^= 1;
            }
        };

        const hold_up = () => {
            this.level = 0x1f;
        };

        const hold_down = () => {
            this.level = 0x00;
        };

        this.cycles = [
            [ ramp_down, hold_down ],
            [ ramp_down, hold_down ],
            [ ramp_down, hold_down ],
            [ ramp_down, hold_down ],
            [ ramp_up  , hold_down ],
            [ ramp_up  , hold_down ],
            [ ramp_up  , hold_down ],
            [ ramp_up  , hold_down ],
            [ ramp_down, ramp_down ],
            [ ramp_down, hold_down ],
            [ ramp_down, ramp_up   ],
            [ ramp_down, hold_up   ],
            [ ramp_up  , ramp_up   ],
            [ ramp_up  , hold_up   ],
            [ ramp_up  , ramp_down ],
            [ ramp_up  , hold_down ],
        ];
    }

    reset() {
        this.counter &= 0;
        this.period  &= 0;
        this.shape   &= 0;
        this.phase   &= 0;
        this.level   &= 0;
    }

    clock() {
        if(++this.counter >= this.period) {
            this.counter &= 0;
            this.cycles[this.shape][this.phase]();
        }
    }

    set_coarse_tune(value) {
        const msb = ((value & 0xff) << 8);
        const lsb = (this.period & (0xff << 0));
        this.period = (msb | lsb);
    }

    set_fine_tune(value) {
        const msb = (this.period & (0xff << 8));
        const lsb = ((value & 0xff) << 0);
        this.period = (msb | lsb);
    }

    set_shape(value) {
        this.shape = value;
        this.phase = 0;
        this.level = ((this.shape & 0x04) != 0 ? 0x00 : 0x1f);
    }

    get_level() {
        return this.level;
    }
}
                            

L'ÉMULATEUR DE AY-3-8910

TONE AN NOISE MIXER


class AYM_ToneAndNoiseMixer {
    constructor() {
        this.sound0 = 0;
        this.noise0 = 0;
        this.level0 = 0;
        this.sound1 = 0;
        this.noise1 = 0;
        this.level1 = 0;
        this.sound2 = 0;
        this.noise2 = 0;
        this.level2 = 0;
    }

    reset() {
        this.sound0 &= 0;
        this.noise0 &= 0;
        this.level0 &= 0;
        this.sound1 &= 0;
        this.noise1 &= 0;
        this.level1 &= 0;
        this.sound2 &= 0;
        this.noise2 &= 0;
        this.level2 &= 0;
    }

    clock(envelope) {
        const level = envelope.get_level();

        if((this.level0 & 0x20) != 0) {
            this.level0 = ((this.level0 & 0xe0) | (level & 0x1f));
        }
        if((this.level1 & 0x20) != 0) {
            this.level1 = ((this.level1 & 0xe0) | (level & 0x1f));
        }
        if((this.level2 & 0x20) != 0) {
            this.level2 = ((this.level2 & 0xe0) | (level & 0x1f));
        }
    }

    set_configuration(value) {
        this.sound0 = (((value & 0x01) == 0) | 0);
        this.sound1 = (((value & 0x02) == 0) | 0);
        this.sound2 = (((value & 0x04) == 0) | 0);
        this.noise0 = (((value & 0x08) == 0) | 0);
        this.noise1 = (((value & 0x10) == 0) | 0);
        this.noise2 = (((value & 0x20) == 0) | 0);
    }

    set_channel0_amplitude(value) {
        this.level0 = ((value << 1) | (value & 0x01));
    }

    set_channel1_amplitude(value) {
        this.level1 = ((value << 1) | (value & 0x01));
    }

    set_channel2_amplitude(value) {
        this.level2 = ((value << 1) | (value & 0x01));
    }
}
                            

L'ÉMULATEUR DE AY-3-8910

CHIP EMULATOR


export class AYM_Emulator {
    constructor(setup) {
        this.state = new AYM_State();
        this.tone0 = new AYM_ToneGenerator();
        this.tone1 = new AYM_ToneGenerator();
        this.tone2 = new AYM_ToneGenerator();
        this.noise = new AYM_NoiseGenerator();
        this.envel = new AYM_EnvelopeGenerator();
        this.mixer = new AYM_ToneAndNoiseMixer();
        this.master_clock = 1000000;
        this.clock_divide = 0;
        this.dac          = AY_DAC;
        this.set_type(setup.type || 'default');
        this.reset();
    }

    set_type(type) {
        switch(type) {
            case 'AY':
                this.set_type_ay();
                break;
            case 'YM':
                this.set_type_ym();
                break;
            default:
                break;
        }
    }

    set_type_ay() {
        this.dac = AY_DAC;
    }

    set_type_ym() {
        this.dac = YM_DAC;
    }

    reset() {
        this.state.reset();
        this.tone0.reset();
        this.tone1.reset();
        this.tone2.reset();
        this.noise.reset();
        this.envel.reset();
        this.mixer.reset();
        this.master_clock |= 0;
        this.clock_divide &= 0;
        for(let index = 0; index < 16; ++index) {
            this.set_register_index(index);
            this.set_register_value(0);
            this.set_register_index(0);
        }
    }

    clock() {
        const clk_div = (this.clock_divide = ((this.clock_divide + 1) & 0xff));
        if((clk_div & 0x07) == 0) {
            this.fixup_tones();
            this.tone0.clock();
            this.tone1.clock();
            this.tone2.clock();
            this.noise.clock();
            this.envel.clock();
            this.mixer.clock(this.envel);
        }
    }

    fixup_tones() {
        const fixup = (lhs, rhs) => {
            if((lhs.period == rhs.period) && (lhs.counter != rhs.counter)) {
                lhs.counter = rhs.counter;
                lhs.phase   = rhs.phase;
            }
        };
        fixup(this.tone0, this.tone1);
        fixup(this.tone0, this.tone2);
        fixup(this.tone1, this.tone2);
    }

    set_register_index(reg_index) {
        this.state.index = (reg_index &= 0xff);

        return reg_index;
    }

    get_register_value(reg_value) {
        const reg_index = this.state.index;
        const reg_array = this.state.array;

        if((reg_index >= 0) && (reg_index <= 15)) {
            reg_value = reg_array[reg_index];
        }
        return reg_value;
    }

    set_register_value(reg_value) {
        const reg_index = this.state.index;
        const reg_array = this.state.array;

        switch(reg_index) {
            case 0x00: /* CHANNEL_A_FINE_TUNE */
                reg_array[reg_index] = (reg_value &= 0xff);
                this.tone0.set_fine_tune(reg_value);
                break;
            case 0x01: /* CHANNEL_A_COARSE_TUNE */
                reg_array[reg_index] = (reg_value &= 0x0f);
                this.tone0.set_coarse_tune(reg_value);
                break;
            case 0x02: /* CHANNEL_B_FINE_TUNE */
                reg_array[reg_index] = (reg_value &= 0xff);
                this.tone1.set_fine_tune(reg_value);
                break;
            case 0x03: /* CHANNEL_B_COARSE_TUNE */
                reg_array[reg_index] = (reg_value &= 0x0f);
                this.tone1.set_coarse_tune(reg_value);
                break;
            case 0x04: /* CHANNEL_C_FINE_TUNE */
                reg_array[reg_index] = (reg_value &= 0xff);
                this.tone2.set_fine_tune(reg_value);
                break;
            case 0x05: /* CHANNEL_C_COARSE_TUNE */
                reg_array[reg_index] = (reg_value &= 0x0f);
                this.tone2.set_coarse_tune(reg_value);
                break;
            case 0x06: /* NOISE_GENERATOR */
                reg_array[reg_index] = (reg_value &= 0x1f);
                this.noise.set_fine_tune(reg_value);
                break;
            case 0x07: /* MIXER_AND_IO_CONTROL */
                reg_array[reg_index] = (reg_value &= 0xff);
                this.mixer.set_configuration(reg_value);
                break;
            case 0x08: /* CHANNEL_A_AMPLITUDE */
                reg_array[reg_index] = (reg_value &= 0x1f);
                this.mixer.set_channel0_amplitude(reg_value);
                break;
            case 0x09: /* CHANNEL_B_AMPLITUDE */
                reg_array[reg_index] = (reg_value &= 0x1f);
                this.mixer.set_channel1_amplitude(reg_value);
                break;
            case 0x0a: /* CHANNEL_C_AMPLITUDE */
                reg_array[reg_index] = (reg_value &= 0x1f);
                this.mixer.set_channel2_amplitude(reg_value);
                break;
            case 0x0b: /* ENVELOPE_FINE_TUNE */
                reg_array[reg_index] = (reg_value &= 0xff);
                this.envel.set_fine_tune(reg_value);
                break;
            case 0x0c: /* ENVELOPE_COARSE_TUNE */
                reg_array[reg_index] = (reg_value &= 0xff);
                this.envel.set_coarse_tune(reg_value);
                break;
            case 0x0d: /* ENVELOPE_SHAPE */
                reg_array[reg_index] = (reg_value &= 0x0f);
                this.envel.set_shape(reg_value);
                break;
            case 0x0e: /* IO_PORT_A */
                reg_array[reg_index] = (reg_value &= 0xff);
                break;
            case 0x0f: /* IO_PORT_B */
                reg_array[reg_index] = (reg_value &= 0xff);
                break;
            default:
                break;
        }
        return reg_value;
    }

    get_master_clock() {
        return this.master_clock;
    }

    set_master_clock(master_clock) {
        this.master_clock = (master_clock | 0);
        if(this.master_clock <= 0) {
            this.master_clock = 1000000;
        }
        return this.master_clock;
    }

    get_channel0() {
        const sound = (this.tone0.phase & this.mixer.sound0);
        const noise = (this.noise.phase & this.mixer.noise0);
        const level = (this.dac[this.mixer.level0 & 0x1f]);

        return ((sound | noise) * level);
    }

    get_channel1() {
        const sound = (this.tone1.phase & this.mixer.sound1);
        const noise = (this.noise.phase & this.mixer.noise1);
        const level = (this.dac[this.mixer.level1 & 0x1f]);

        return ((sound | noise) * level);
    }

    get_channel2() {
        const sound = (this.tone2.phase & this.mixer.sound2);
        const noise = (this.noise.phase & this.mixer.noise2);
        const level = (this.dac[this.mixer.level2 & 0x1f]);

        return ((sound | noise) * level);
    }
}
                            

L'ÉMULATEUR DE AY-3-8910

AYM AUDIO WORKLET PROCESSOR


export class AYM_Processor extends AudioWorkletProcessor {
    constructor(options) {
        /* code */
    }

    process(inputs, outputs, parameters) {
        /* code */
    }
}
                            

L'ÉMULATEUR DE AY-3-8910

AYM AUDIO WORKLET PROCESSOR


export class AYM_Processor extends AudioWorkletProcessor {
    constructor(options) {
        super();
        this.chip       = new AYM_Emulator();
        this.channel_a  = new Float32Array(256);
        this.channel_b  = new Float32Array(256);
        this.channel_c  = new Float32Array(256);
        this.count      = 0;
        this.clock      = 1000000;
        this.chip.set_master_clock(this.clock);
        this.chip.reset();
        this.port.onmessage = (message) => {
            /* process messages here */
        };
    }

    process(inputs, outputs, parameters) {
        /* code */
    }
}
                            

L'ÉMULATEUR DE AY-3-8910

AYM AUDIO WORKLET PROCESSOR


export class AYM_Processor extends AudioWorkletProcessor {
    constructor(options) {
        /* code */
    }

    process(inputs, outputs, parameters) {
        const count = outputs[0][0].length;
        for(let index = 0; index < count; ++index) {
            this.channel_a[index] = this.chip.get_channel0();
            this.channel_b[index] = this.chip.get_channel1();
            this.channel_c[index] = this.chip.get_channel2();
            while(this.count < this.clock) {
                this.count += sampleRate;
                this.chip.clock();
            }
            this.count -= this.clock;
        }
        for(const output of outputs) {
            if(output.length >= 2) {
                this.mixStereo(output[0], output[1]);
                continue;
            }
            if(output.length >= 1) {
                this.mixMono(output[0]);
                continue;
            }
        }
        return true;
    }
}
                            

AYM-JS

UN LECTEUR DE MUSIQUE

UN LECTEUR DE MUSIQUE

EN AVANT LA MUSIQUE

aym-player-play

L'API Web MIDI

YOUR BROWSER · YOUR HOME STUDIO

L'API Web MIDI

keyboard

  • Premier draft du W3C en 2013
  • Dernière version le 17 mars 2015
  • Apporte le protocole MIDI dans le navigateur
  • Permet de piloter des instruments de musique
  • Permet de devenir un instrument de musique

L'API Web MIDI

UTILISATION


function midiInitialize() {
    /* code */
}

function midiSuccess(midi) {
    /* code */
}

function midiFailure(midi) {
    /* code */
}
                            

L'API Web MIDI

UTILISATION


function midiInitialize() {
    navigator.requestMIDIAccess().then(
        (midi) => { midiSuccess(midi) },
        (midi) => { midiFailure(midi) }
    );
}

function midiSuccess(midi) {
    /* code */
}

function midiFailure(midi) {
    /* code */
}
                            

L'API Web MIDI

UTILISATION


function midiInitialize() {
    /* code */
}

function midiSuccess(midi) {
    midi.inputs.forEach((input) => {
        input.onmidimessage = (message) => {
            switch((message.data[0] >> 4)) {
                case 0x8: midiNoteOff(message);
                    break;
                case 0x9: midiNoteOn(message);
                    break;
                case 0xa: midiAftertouch(message);
                    break;
                case 0xb: midiControlChange(message);
                    break;
                case 0xc: midiProgramChange(message);
                    break;
                case 0xd: midiChannelPressure(message);
                    break;
                case 0xe: midiPitchBend(message);
                    break;
                case 0xf: midiSystemControl(message);
                    break;
                default:
                    break;
            }
        };
    });
}

function midiFailure(midi) {
    /* code */
}
                            

L'API Web MIDI

UTILISATION


function midiInitialize() {
    /* code */
}

function midiSuccess(midi) {
    /* code */
}

function midiFailure(midi) {
    throw new Error('Error while requesting MIDI access!');
}
                            

UN EXEMPLE SIMPLE

PLACE AU TEST

example03-play

AYM-JS

UN SYNTHÉTISEUR MIDI POLYPHONIQUE

UN SYNTHÉTISEUR MIDI POLYPHONIQUE

EN AVANT LA MUSIQUE

aym-synth-play

POUR CONCLURE

THE SOUND OF SILENCE

RETROUVEZ LE PROJET

  • aym-js.emaxilde.net
  • github.com/ponceto/aym-js
  • gitlab.com/ponceto/aym-js
  • bitbucket.org/ponceto/aym-js

qrcode

MERCI

@ponceto91 emaxilde.net
@ponceto91 github.com/ponceto/
@ponceto91 gitlab.com/ponceto/
@ponceto91 bitbucket.org/ponceto/

qrcode