..

author: sbt date: 2014-05-25 19:11:48+00:00 title: son [ms]pacman category: abandonware tags: pacman, python

son [ms]pacman

J'ai découvert avec étonnement le travail  de Shaun Williams de reimplementation de la serie des "pacman" en langage moderne. Le résultat actuel de son travail est ici : http://pacman.shaunew.com/ Par ailleurs il a décidé d'utiliser la licence GNU General Public License Version 3 de la Free Software Foundation.

Je devais contribuer....et il restait le son à reproduire.

J'ai alors trouvé le travail de Frederic Vecoven qui a traduit le code assembleur Z80 de la partie sonore en PIC18 pour un projet personnel.

Je suis donc reparti de son travail pour remonter manuellement à une version haut niveau en python3.

Il reste à terminer l'optimisation du code ainsi qu'à supprimer des données son historique les détails d'implémentation.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright 2014 Sebastien Bechet

#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License Version 3 as
#  published by the Free Software Foundation.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.

# [ms][Pac] WSG Sound Generator and more ....
# Proof of concept - version 20140714

# Thank you :
# Shaun LeBron shaunewilliams@gmail.com (http://shaunlebron.com)
# Scott Lawrence (yorgle@gmail.com) (http://code.google.com/p/bleu-romtools/) : Hardware.txt
# Alessandro Scotti (dev@ascotti.org) (http://walkofmind.com/programming/pie/wsg3.htm)
# Frederic Vecoven (frederic@vecoven.com) (http://www.vecoven.com/elec/pacman/pacman.html)

import struct, wave, os, pdb
from ctypes import c_ubyte, c_byte

#~ debug:
#~ import pdb
#~ import perfectsound
#~ pdb.run('perfectsound.main()')
#~ breakpoint: pdb.set_trace()

class Bank(object):
    song = None    # default : no song
    wave = 0    # wave number
    vol = 0     # 1 - volume
    freq = 0    # 2 - frequency

    voice = 0   # WSG voice number

    # Volume decrease
    # [no, -1 per rate, -1 per 1/2 rate]
    volDecrease = 0

    def __init__(self, data, v):
        self.song = data
        self.voice = v
        self.wave= self.vol = self.freq = 0

    def debug(self):
        print('[', self.voice, ',', self.wave, ',', self.getFrequency(), ',', self.vol, ']', end='')

    def getWave(self):
        return self.wave

    def getVoice(self):
        return self.voice

    def getVolume(self):
        return self.vol

    # TODO: Maybe a day, modify directly data sound
    # for voices to remove historic implementation ?
    def getFrequency(self):
        if self.voice == 0:
            return abs(self.freq)
        else:
            return abs(16 * self.freq)

    def updateVolume(self, refreshCount):
        if self.volDecrease == 0: # constant volume
            return self.vol
        if self.volDecrease == 1: # decreasing volume
            if self.vol > 0:
                self.vol -= 1
        elif self.volDecrease == 2: # -1 per 1/2 rate
            if self.vol > 0 and refreshCount%60:
                self.vol -= 1
        #  -1 per 1/4 rate, -1 per 1/8 rate : never happend in music data
        return self.vol

    def isFinished(self):
        if self.song == None:
            return True
        else:
            return False

    def update(self, refreshCount):
        updateVolume(self, refreshCount)

class Effect(Bank):
    def __init__(self, data, voice):
        Bank.__init__(self, data, voice)
        self.wave = self.song[0] & 0x7
        self.vol = self.freq = 0
        self.reverseFlipFlap = True
        self.duration = self.song[3] & 0x7f
        self.baseFreq = self.song[1]
        self.volDecrease = (self.song[6] >> 4) & 0x0f
        self.song[6] &= 0x0f
        self.vol = self.song[6]
        self.reverseFlipFlap = True

    def update(self, refreshCount):
        if self.song == None:
            return 0
        self.duration -= 1;
        if self.duration == 0:
            # duration is now 0, check repeat
            self.song[5] -= 1
            if self.song[5] <= 0:
                self.song = None
                return 0
            # duration is 0. Re-init it.
            self.duration = self.song[3] & 0x7f

            # Reverse Frequency increment
            if (self.song[3] & 0x80) != 0:
                self.song[2] = - self.song[2]
                if self.reverseFlipFlap == True:
                    self.reverseFlipFlap=False
                else:
                    self.reverseFlipFlap=True

                    self.song[1] += self.song[4]
                    self.baseFreq = self.song[1]
                    self.song[6] += self.song[7]
            else:
                    self.song[1] += self.song[4]
                    self.baseFreq = self.song[1]
                    self.song[6] += self.song[7]

        self.baseFreq += self.song[2]
        self.freq = self.baseFreq * (1<<((self.song[0]&0x70)>>4))
        self.updateVolume(refreshCount)
        return self.vol

class Music(Bank):

    freq_table = [0x00, 87, 0x5c, 0x61, 0x67, 0x6d, 0x74, 0x7b,
                  0x82, 0x8a, 0x92, 0x9a, 0xa3, 0xad, 0xb8, 0xc3]
    loop = True

    # loop : accept loop ?
    def __init__(self, data, voice, notes, loop):
        Bank.__init__(self, data, voice)
        self.notesSource = notes
        self.loop = loop
        self.wave = self.vol = self.freq = 0
        self.duration =  0
        self.baseVol = self.baseFreq = 0
        self.baseWaveScaleBis = self.baseWaveScale  = self.WaveScale =   0
        self.songSeek = 0
        self.getNextValue()
        self.WaveScale = self.baseWaveScaleBis
        self.WaveScale += self.baseWaveScale
        self.freq = self.baseFreq * (1<<self.WaveScale)
        self.debug = True
        self.showdebug()

    def showdebug(self):
        if self.debug:
            gfreq = self.freq
            if self.voice != 0:
                gfreq = 16 * self.freq
            print("[", end='')
            print(self.wave, sep=', ', end='')
            print(', ', self.notesSource[gfreq], ', ', sep='"', end='')
            print(self.duration, self.baseVol, self.volDecrease, sep=', ', end='')
            print("],", end='\n')
            self.duration = 1

    def update(self, refreshCount):
        if self.song == None:
            return 0
        self.duration -= 1
        if self.duration == 0:
            if not self.getNextValue():
                return 0
            self.WaveScale = self.baseWaveScaleBis
            self.WaveScale += self.baseWaveScale
            self.freq = self.baseFreq * (1<<self.WaveScale)
            self.showdebug()

        self.updateVolume(refreshCount)
        return self.vol
    #------------------------------------------------------------------
    def getNextValue(self):
        value = self.song[self.songSeek]
        self.songSeek += 1
        while value >= 0x0F0:
            if value == 0x0F0:
                # address where song continues (allows loop or jump in rom)
                if self.loop:
                    self.songSeek = self.song[self.songSeek]
                else:
                    # mark end of song
                    self.vol = self.freq = 0
                    self.song = None
                    return False
            elif value == 0x0F1:
                # wave select
                self.wave = self.song[self.songSeek] & 0x07
                self.songSeek += 1
            elif value == 0x0F2:
                # frequency increment
                self.baseWaveScale = self.song[self.songSeek]
                self.songSeek += 1
            elif value == 0x0F3:
                # volume
                self.baseVol = self.song[self.songSeek]
                self.songSeek += 1
            elif value == 0x0F4:
                # type 0, 1 ou 2
                self.volDecrease = self.song[self.songSeek]
                self.songSeek += 1
            elif value == 0x0FF:
                # mark end of song
                self.vol = self.freq = 0
                self.song = None
                return False
            value = self.song[self.songSeek]
            self.songSeek += 1
        self.baseWaveScaleBis = (value&0x10) >> 4
        self.vol = self.baseVol
        # upper 3 bits = duration (power of 2 of these bits = duration)
        self.duration = 1<<((value & 0x0E0) >> 5)
        # lower 4 bits = base frequency (using a lookup table)
        if (value & 0x1f) != 0:
            self.baseFreq = self.freq_table[value & 0x0f]
        return True

class data(object):
    #~ 0 : upper 3 bits = frequency shift, lower 3 bits = wave select
    #~ 1 : initial base frequency
    #~ 2 : frequency increment (added to base freq)
    #~ 3 : upper bit = reverse lower 7 bits = duration
    #~ 4 : frequency increment (added to initial base frequency). Used when repeat > 1
    #~ 5 : repeat
    #~ 6 : upper 4 bits = volume adjust type lower 4 bits = volume
    #~ 7 : volume increment

    # first table = voice possibility
    # TODO : add directly frequency shift to initial base frequency and remove old code

    effect_dict = {
        'pac_0_freelife':   [ [0], [0x73,  32,  0, 0x0C,  0, 10, 0x1f,  0] ],
        'pac_1_credit':     [ [0], [0x72,  32, -5, 0x87,  0,  2, 0x0f,  0] ],
        'pac_2_siren1':     [ [1], [0x36,  32,  4, 0x8C,  0,  0, 0x06,  0] ],
        'pac_3_siren2':     [ [1], [0x36,  40,  5, 0x8B,  0,  0, 0x06,  0] ],
        'pac_4_siren3':     [ [1], [0x36,  48,  6, 0x8A,  0,  0, 0x06,  0] ],
        'pac_5_siren4':     [ [1], [0x36,  60,  7, 0x89,  0,  0, 0x06,  0] ],
        'pac_6_siren5':     [ [1], [0x36,  72,  8, 0x88,  0,  0, 0x06,  0] ],
        'pac_7':            [ [1], [0x24,   0,  6, 0x08,  0,  0, 0x0A,  0] ],
        'pac_8_eatghost2':  [ [1], [0x40, 112, -6, 0x10,  0,  0, 0x0A,  0] ],
        #'pac_9':            [ [1], [0x70,   4,  0, 0x00,  0,  0, 0x08,  0] ], # every time the same tone (750Hz?)
        'pac_A_eatdot1':    [ [2], [0x42,  24, -3, 0x06,  0,  1, 0x0C,  0] ],
        'pac_B_eatdot2':    [ [2], [0x42,   4,  3, 0x06,  0,  1, 0x0C,  0] ],
        'pac_C_eatfruit':   [ [2], [0x56,  12, -1, 0x8C,  0,  2, 0x0F,  0] ],
        'pac_D_eatghost':   [ [2], [0x05,   0,  2, 0x20,  0,  1, 0x0C,  0] ],
        'pac_E_death':      [ [2], [0x41,  32, -1, 0x86, -2, 28, 0x0F, -1] ],
        'pac_F':            [ [2], [0x70,   0,  1, 0x0C,  0,  1, 0x08,  0] ],

        'ms_0_freelife':    [ [0], [0x73,  32,    0, 0x0c,  0, 10, 0x1f,  0] ], # idem pac_0_freelife
        'ms_1_credit':      [ [0], [0x72,  32,   -5, 0x87,  0,  2, 0x0f,  0] ], # idem pac_1_credit
        'ms_2_siren1':      [ [1], [0x59,   1,    6, 0x08,  0,  0, 0x02,  0] ],
        'ms_3_siren2':      [ [1], [0x59,   1,    6, 0x09,  0,  0, 0x02,  0] ],
        'ms_4_siren3':      [ [1], [0x59,   2,    6, 0x0a,  0,  0, 0x02,  0] ],
        'ms_5_siren4':      [ [1], [0x59,   3,    6, 0x0b,  0,  0, 0x02,  0] ],
        'ms_6_siren5':      [ [1], [0x59,   4,    6, 0x0c,  0,  6, 0x02,  0] ],
        'ms_7_bg_blue':     [ [0], [0x24,   0,    6, 0x08,  2,  0, 0x0a,  0] ], # blue ghosts background # XXX:good voice number ?
        'ms_8_bg_ghosts':   [ [0], [0x36,   7, -121, 0x6f,  0,  0, 0x04,  0] ], # background when a ghost is under eye form # XXX:good voice number ?
        #'ms_9':             [ [1], [0x70,   4,    0, 0x00,  0,  0, 0x08,  0] ], # every time the same tone (750Hz?)
        'ms_A_eatdot':      [ [0], [0x1c, 112, -117, 0x08,  0,  1, 0x06,  0] ], # XXX:good voice number ?
        'ms_B_eatdot':      [ [0], [0x1c, 112, -117, 0x08,  0,  1, 0x06,  0] ], # XXX:good voice number ?
        'ms_C_eatfruit':    [ [2], [0x56,  12,   -1, 0x8c,  0,  2, 0x08,  0] ],
        'ms_D_eatghost':    [ [2], [0x56,   0,    2, 0x0a,  7,  3, 0x0c,  0] ],
        'ms_E_death':       [ [2], [0x36,  56,   -2, 0x12, -8,  4, 0x0f, -4] ],
        'ms_F_fruitmove':   [ [2], [0x22,   1,    1, 0x06,  0,  1, 0x07,  0] ],
    }

    # first table are voice number for next tables
    music_dict = {
        'pac_start' : [ [0,1],
            [[0xf1, 0x02, 0xf2, 0x03, 0xf3, 0x0f, 0xf4, 0x01,
            0x82, 0x70, 0x69, 0x82, 0x70, 0x69, 0x83, 0x70,
            0x6a, 0x83, 0x70, 0x6a, 0x82, 0x70, 0x69, 0x82,
            0x70, 0x69, 0x89, 0x8b, 0x8d, 0x8e, 0xff],
            [0xf1, 0x00, 0xf2, 0x02, 0xf3, 0x0f, 0xf4, 0x00,
            0x42, 0x50, 0x4e, 0x50, 0x49, 0x50, 0x46, 0x50,
            0x4e, 0x49, 0x70, 0x66, 0x70, 0x43, 0x50, 0x4f,
            0x50, 0x4a, 0x50, 0x47, 0x50, 0x4f, 0x4a, 0x70,
            0x67, 0x70, 0x42, 0x50, 0x4e, 0x50, 0x49, 0x50,
            0x46, 0x50, 0x4e, 0x49, 0x70, 0x66, 0x70, 0x45,
            0x46, 0x47, 0x50, 0x47, 0x48, 0x49, 0x50, 0x49,
            0x4a, 0x4b, 0x50, 0x6e, 0xff]]
        ],
        'pac_act' : [ [0,1],
            [[0xf1, 0x02, 0xf2, 0x03, 0xf3, 0x0f, 0xf4, 0x01,
            0x67, 0x50, 0x30, 0x47, 0x30, 0x67, 0x50, 0x30,
            0x47, 0x30, 0x67, 0x50, 0x30, 0x47, 0x30, 0x4b,
            0x10, 0x4c, 0x10, 0x4d, 0x10, 0x4e, 0x10, 0x67,
            0x50, 0x30, 0x47, 0x30, 0x67, 0x50, 0x30, 0x47,
            0x30, 0x67, 0x50, 0x30, 0x47, 0x30, 0x4b, 0x10,
            0x4c, 0x10, 0x4d, 0x10, 0x4e, 0x10, 0x67, 0x50,
            0x30, 0x47, 0x30, 0x67, 0x50, 0x30, 0x47, 0x30,
            0x67, 0x50, 0x30, 0x47, 0x30, 0x4b, 0x10, 0x4c,
            0x10, 0x4d, 0x10, 0x4e, 0x10, 0x77, 0x20, 0x4e,
            0x10, 0x4d, 0x10, 0x4c, 0x10, 0x4a, 0x10, 0x47,
            0x10, 0x46, 0x10, 0x65, 0x30, 0x66, 0x30, 0x67,
            0x40, 0x70, 0xf0, 0x08],
            [0xf1, 0x01, 0xf2, 0x01, 0xf3, 0x0f, 0xf4, 0x00,
            0x26, 0x67, 0x26, 0x67, 0x26, 0x67, 0x23, 0x44,
            0x42, 0x47, 0x30, 0x67, 0x2a, 0x8b, 0x70, 0x26,
            0x67, 0x26, 0x67, 0x26, 0x67, 0x23, 0x44, 0x42,
            0x47, 0x30, 0x67, 0x23, 0x84, 0x70, 0x26, 0x67,
            0x26, 0x67, 0x26, 0x67, 0x23, 0x44, 0x42, 0x47,
            0x30, 0x67, 0x29, 0x6a, 0x2b, 0x6c, 0x30, 0x2c,
            0x6d, 0x40, 0x2b, 0x6c, 0x29, 0x6a, 0x67, 0x20,
            0x29, 0x6a, 0x40, 0x26, 0x87, 0x70, 0xf0, 0x08]]
        ],
        'ms_start' : [ [0,1],
            [[0xf1, 0x02, 0xf2, 0x03, 0xf3, 0x0a, 0xf4, 0x00,
            0x50, 0x70, 0x86, 0x90, 0x81, 0x90, 0x86, 0x90,
            0x68, 0x6a, 0x6b, 0x68, 0x6a, 0x68, 0x66, 0x6a,
            0x68, 0x66, 0x65, 0x68, 0x86, 0x81, 0x86, 0xff],
            [0xf1, 0x00, 0xf2, 0x02, 0xf3, 0x0a, 0xf4, 0x00,
            0x41, 0x43, 0x45, 0x86, 0x8a, 0x88, 0x8b, 0x6a,
            0x6b, 0x71, 0x6a, 0x88, 0x8b, 0x6a, 0x6b, 0x71,
            0x6a, 0x6b, 0x71, 0x73, 0x75, 0x96, 0x95, 0x96,
            0xff]]
        ],
        'ms_act1' : [ [0,1],
            [[0xf1, 0x03, 0xf2, 0x03, 0xf3, 0x0a, 0xf4, 0x02,
            0x70, 0x66, 0x70, 0x46, 0x50, 0x86, 0x90, 0x70,
            0x66, 0x70, 0x46, 0x50, 0x86, 0x90, 0x70, 0x66,
            0x70, 0x46, 0x50, 0x86, 0x90, 0x70, 0x61, 0x70,
            0x41, 0x50, 0x81, 0x90, 0xf4, 0x00, 0xa6, 0xa4,
            0xa2, 0xa1, 0xf4, 0x01, 0x86, 0x89, 0x8b, 0x81,
            0x74, 0x71, 0x6b, 0x69, 0xa6, 0xff],
            [0xf1, 0x00, 0xf2, 0x02, 0xf3, 0x0a, 0xf4, 0x00,
            0x69, 0x6b, 0x69, 0x86, 0x61, 0x64, 0x65, 0x86,
            0x86, 0x64, 0x66, 0x64, 0x61, 0x69, 0x6b, 0x69,
            0x86, 0x61, 0x64, 0x64, 0xa1, 0x70, 0x71, 0x74,
            0x75, 0x35, 0x76, 0x30, 0x50, 0x35, 0x76, 0x30,
            0x50, 0x54, 0x56, 0x54, 0x51, 0x6b, 0x69, 0x6b,
            0x69, 0x6b, 0x91, 0x6b, 0x69, 0x66, 0xf2, 0x01,
            0x74, 0x76, 0x74, 0x71, 0x74, 0x71, 0x6b, 0x69,
            0xa6, 0xa6, 0xff]]
        ],
        'ms_act2' : [ [0,1],
            [[0xf1, 0x03, 0xf2, 0x03, 0xf3, 0x0a, 0xf4, 0x02,
            0x90, 0x7c, 0x7b, 0x7a, 0x79, 0x79, 0x78, 0x97,
            0x76, 0x75, 0x74, 0x73, 0x73, 0x72, 0x91, 0xa8,
            0x88, 0x60, 0x4a, 0x4c, 0x91, 0x95, 0x88, 0x95,
            0x91, 0x95, 0x88, 0x95, 0x91, 0x95, 0x88, 0x95,
            0x95, 0x98, 0x94, 0x97, 0x93, 0x96, 0x88, 0x96,
            0x93, 0x96, 0x88, 0x96, 0x93, 0x96, 0x88, 0x96,
            0xb6, 0xb3, 0x75, 0x76, 0x77, 0x78, 0x78, 0x75,
            0x73, 0x68, 0x91, 0x95, 0x88, 0x95, 0x91, 0x95,
            0x88, 0x95, 0x86, 0x96, 0x95, 0x92, 0x93, 0x8c,
            0x8a, 0x88, 0x86, 0x90, 0x90, 0x96, 0x95, 0x90,
            0x90, 0x86, 0x90, 0x96, 0x90, 0x96, 0x91, 0x88,
            0x81, 0xff],
            [0xf1, 0x00, 0xf2, 0x02, 0xf3, 0x0a, 0xf4, 0x00,
            0x88, 0x6c, 0x71, 0x72, 0x73, 0x73, 0x71, 0x93,
            0x6c, 0x73, 0x75, 0x76, 0x76, 0x75, 0x96, 0x7c,
            0x7a, 0x78, 0x76, 0x75, 0x96, 0x6c, 0x91, 0xa0,
            0x88, 0x75, 0x76, 0x77, 0x78, 0x71, 0x73, 0x74,
            0x75, 0x71, 0x75, 0x71, 0x68, 0x68, 0x65, 0x66,
            0x67, 0xa8, 0xab, 0xac, 0x8c, 0x86, 0x76, 0x75,
            0x6c, 0x71, 0x75, 0x73, 0x6b, 0x6c, 0x73, 0x76,
            0x7a, 0x78, 0x78, 0x76, 0x73, 0x6c, 0xaa, 0xa8,
            0x71, 0x73, 0x74, 0x75, 0x6a, 0x6b, 0x6c, 0x73,
            0x75, 0x76, 0x77, 0x78, 0x71, 0x73, 0x74, 0x75,
            0x71, 0x75, 0x71, 0x68, 0x48, 0x40, 0x68, 0x67,
            0x68, 0xaa, 0xa9, 0xaa, 0x6a, 0x60, 0x8a, 0x76,
            0x75, 0x73, 0x71, 0x71, 0x73, 0x95, 0x75, 0x73,
            0x71, 0x68, 0x68, 0x61, 0x63, 0x6a, 0xa8, 0x6c,
            0x76, 0x6a, 0x6c, 0x91, 0x90, 0x91, 0xff]]
        ],
        'ms_act3' : [ [0,1],
            [[0xf1, 0x02, 0xf2, 0x03, 0xf3, 0x0a, 0xf4, 0x02,
            0x65, 0x90, 0x68, 0x70, 0x68, 0x67, 0x66, 0x65,
            0x90, 0x61, 0x70, 0x61, 0x65, 0x68, 0x66, 0x90,
            0x63, 0x90, 0x86, 0x90, 0x85, 0x90, 0x85, 0x70,
            0x86, 0x68, 0x65, 0xff],
            [0xf1, 0x00, 0xf2, 0x02, 0xf3, 0x0a, 0xf4, 0x00,
            0x65, 0x64, 0x65, 0x88, 0x67, 0x88, 0x61, 0x63,
            0x64, 0x85, 0x64, 0x85, 0x6a, 0x69, 0x6a, 0x8c,
            0x75, 0x93, 0x90, 0x91, 0x90, 0x91, 0x70, 0x8a,
            0x68, 0x71, 0xff]]
        ]
    }

    notes_source = {
        348: 'C-', 368: 'C#', 388: 'D-', 412: 'D#', 436: 'E-', 464: 'F-',
        492: 'F#', 520: 'G-', 552: 'G#', 584: 'A-', 616: 'A#', 652: 'B-'
    }

    def __init__(self):
        self.notes = {}
        for k, v in self.notes_source.items():
            k2 = int(k / 8)
            v2 = v + '0'
            self.notes[k2] = v2
            k2 = int(k / 4)
            v2 = v + '1'
            self.notes[k2] = v2
            k2 = int(k / 2)
            v2 = v + '2'
            self.notes[k2] = v2
            k2 = k
            v2 = v + '3'
            self.notes[k2] = v2
            for i in (1, 2, 3, 4, 5, 6):
                k2 = k * pow(2,i)
                v2 = v + str(i+3)
                self.notes[k2] = v2
        self.notes[1384] = 'Cn5' # bug in historic pacman - correct from music point of view
        self.notes[5536] = 'Cn7' # bug in historic pacman
        self.notes[12480] = 'Dn8' # bug in historic pacman
        self.notes[0] = '---'
        print(self.notes)

    def effect_cleanup():
        print("# wave, freqstart, freqinc, duration, reverse, freqincrepeat, repeat, voltype, volval, volinc")
        print("effects = {")
        for neffect in sorted(data.effect_dict):
            freq_multiplicateur = 1<<((data.effect_dict[neffect][1][0]&0x70)>>4)
            if data.effect_dict[neffect][0][0] != 0:
                freq_multiplicateur = freq_multiplicateur * 16
            wave=data.effect_dict[neffect][1][0] & 0x7
            freqstart=data.effect_dict[neffect][1][1] * freq_multiplicateur
            freqinc=data.effect_dict[neffect][1][2] * freq_multiplicateur
            duration=data.effect_dict[neffect][1][3] & 0x7f
            reverse=(data.effect_dict[neffect][1][3] & 0x80) >> 7
            freqincrepeat=data.effect_dict[neffect][1][4] * freq_multiplicateur
            repeat=data.effect_dict[neffect][1][5]
            voltype=data.effect_dict[neffect][1][6] >> 4
            volval=data.effect_dict[neffect][1][6] & 0x0f
            volinc=data.effect_dict[neffect][1][7]
            # version sans le multiplicateur de 2^15
            freqstart = 96000*freqstart/pow(2,15)
            freqinc = 96000*freqinc/pow(2,15)
            freqincrepeat = 96000*freqincrepeat/pow(2,15)
            print("    '" + neffect + "' : [", end='')
            print(wave, freqstart, freqinc, duration, reverse, freqincrepeat, repeat, voltype, volval, volinc, sep=',', end='')
            print("]")
        print("}")

    def music_cleanup():
        vd = data()
        print("# key : [ [ [wave, note, duration (µs), volume, volEffect], ...] ...] ...")
        print("musics = {")
        for nmusic in sorted(data.music_dict):
            print("'" + nmusic + "' : [", end='')
            for i, voice in enumerate(data.music_dict[nmusic][0]):
                    print("[", end='')
                    m = Music(data.music_dict[nmusic][1][i], voice, vd.notes, False)
                    while m.song != None:
                        m.update(0)
                    print("],",end='')
            print("],")
        print("}")

class Sound(object):
    wavetable = [
        [7,9,10,11,12,13,13,14,14,14,13,13,12,11,10,9,7,5,4,3,2,1,1,0,0,0,1,1,2,3,4,5],
        [7,12,14,14,13,11,9,10,11,11,10,9,6,4,3,5,7,9,11,10,8,5,4,3,3,4,5,3,1,0,0,2],
        [7,10,12,13,14,13,12,10,7,4,2,1,0,1,2,4,7,11,13,14,13,11,7,3,1,0,1,3,7,14,7,0],
        [7,13,11,8,11,13,9,6,11,14,12,7,9,10,6,2,7,12,8,4,5,7,2,0,3,8,5,1,3,6,3,1],
        [0,8,15,7,1,8,14,7,2,8,13,7,3,8,12,7,4,8,11,7,5,8,10,7,6,8,9,7,7,8,8,7],
        [7,8,6,9,5,10,4,11,3,12,2,13,1,14,0,15,0,15,1,14,2,13,3,12,4,11,5,10,6,9,7,8],
        [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0],
        [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
    ]

    debug = False
    banks = []
    accumulators = []

    # Original default outputFrequency is 96000 Hz
    def __init__(self, BankList, outputFrequency, debug):
        self.debug = debug
        self.banks = BankList
        for i in range(len(BankList)):
            self.accumulators.append(0)
        self.chip_frequency = 3072000/32 # Hz (Z80 CPU clock / 32)
        self.vblank = int(outputFrequency / 60)
        self.outputFreqStep = int((int(self.chip_frequency) << 10) / outputFrequency)
        self.count = 0
        return

    # TODO: optimize ... one day
    def update(self):
        sequence = []
        for i in range(int(self.chip_frequency)):
            self.output = 0
            for j, bank in enumerate(self.banks):
                offset_step = bank.getFrequency() * self.outputFreqStep;
                if self.count%self.vblank == 0:
                    if bank.isFinished():
                        self.banks.pop(j)
                        continue
                    else:
                        bank.update(self.count)
                        if self.debug:
                            bank.debug()
                if (bank.getVolume() == 0) or (bank.getFrequency() == 0):
                   self.accumulators[j] = 0
                else:
                    tvol = bank.getVolume()
                    twavetable = Sound.wavetable[bank.getWave()]
                    twavetable_index = (self.accumulators[j]>>25)&0x1F # shift 15 bits + 10 bits to resample
                    # - 8 to sign wave sample value (2^4 bits / 2)
                    self.output +=  tvol * (twavetable[twavetable_index] - 8)
                    self.accumulators[j] += offset_step
            if self.debug and self.count%self.vblank == 0:
                print()
            if len(self.banks) == 0:
                return sequence
            sequence.append(self.output)
            self.count += 1
        return sequence

    def isFinished(self):
        return len(self.banks)==0

    def waveCleanup():
        print("wavetable = [")
        for wave in Sound.wavetable:
            print("\t[",end='')
            for j in wave:
                print(j-8,",",sep='',end='')
                #print(j-8, sep=',',end='')
            print("],")
        print("]")

class SoundWave(Sound):
    wave_file = None

    coefindex = 0
    coefs16bits = [int(32767/(16*16)), int(32767/(16*16*2)), int(32767/(16*16*3))]

    def __init__(self, BankList, outputFrequency, name, debug):
        Sound.__init__(self, BankList, outputFrequency, debug)
        self.coefindex = len(BankList) - 1
        self.wav_file = wave.open(name + ".wav", "w")
        nchannels = 1
        sampwidth = 2
        framerate = outputFrequency
        nframes = 0 # this is a library problem, not my own
        comptype = "NONE"
        compname = "not compressed"
        self.wav_file.setparams((nchannels, sampwidth, framerate, nframes,
            comptype, compname))

    def __del__(self):
        self.wav_file.close()

    def update(self):
        seq = Sound.update(self)
        # expand to 16-bit
        seq16 = []
        for s in seq:
            seq16.append(self.coefs16bits[self.coefindex] * s)
        # then pack to write
        seqb = bytes()
        seqb = seqb.join(struct.pack('h', s) for s in seq16)
        self.wav_file.writeframes(seqb)

def genEffect(destFreq,debug):
    if debug:
        dname = 'ms_0_freelife'
        dvoices = data.effect_dict[dname][0]
        deffect = data.effect_dict[dname][1]
        e = Effect(deffect, dvoices[0])
        s = SoundWave([e], destFreq, dname, debug)
        while not s.isFinished():
            seq = s.update()
    else:
        for i in data.effect_dict:
            print("===" + i + "===")
            dname = 'waves/' + i
            dvoices = data.effect_dict[i][0]
            deffect = data.effect_dict[i][1]

            e = Effect(deffect, dvoices[0])
            s = SoundWave([e], destFreq, dname, debug)
            while not s.isFinished():
                seq = s.update()

def genMusic(destFreq):
    for i in sorted(data.music_dict):
        print("===" + i + "===")
        dname = 'waves/' + i
        dvoices = data.music_dict[i][0]
        dmusics = data.music_dict[i][1]
        m = []
        for i in range(len(dvoices)):
            m.append(Music(dmusics[i], dvoices[i], not debug, False))
        s = SoundWave(m, destFreq, dname, debug)
        while not s.isFinished():
            seq = s.update()

if __name__ == '__main__':
    destFreq = 48000 # Original Frequency is 96000 Hz
    debug = True
    os.makedirs('waves',exist_ok=True)

    Sound.waveCleanup()
    #data.effect_cleanup()
    #data.music_cleanup()
    # begining with effects
    genEffect(destFreq,debug)
    # then musics
    genMusic(destFreq)