资源说明:Hand-crafted music visualization using several thousand particles per second
# Hand-Crafted Particle Visualization to Music, via SDL, OpenGL, and Lua ### There's a demo! You want a demo? Sure you want a demo! Take a look in the output directory for a file named "bowser.avi". This file will, over time, become the final product for this project. ## A little bit of background information This project is a glorified script-able particle engine intended for providing a complex visualization for songs. Because "trans-coding" audio to a MIDI format is practically impossible, all of the scheduling--that is, writing the script to emit the particles--must be done by hand. This is where a keen ear and substantial knowledge of music theory comes in, neither of which I claim to have. This leads to an interesting dichotomy: one one hand, simpler songs would be easier to schedule, but they would lead to a fairly boring visualization. On the other hand, however, complex songs require a lot of work to figure out what's going on. I hope I've hit an acceptable compromise in the song I've chosen: the Bowser theme from Super Mario World 2: Yoshi's Island. Being a theme from an old video game, the theme is not overly complex. It also has a repeat point, meaning I can drag on the visualization for as long as I please. In this specific project, I schedule two minutes and 43.69 seconds. ## Compiling this thing This project depends on SDL, OpenGL, and Lua. There are several $(MAKE) targets, including ```all```, ```debug```, ```valgrind```, etc. A simple ```make all``` should do the trick just fine. ## Running this thing This program has just one argument for intended usage: ```-l```. This argument loads the Lua script ``` ``` and executes it. For information on writing scripts, see any of the Lua files in the ```test``` directory, the ```lua``` directory, or the section ```Writing your own scripts``` below. ### Running the final product To see the final product, run this program as the following: ```$ ./vis -l lua/bowser.lua``` Pressing ```^D``` (```Control+D```) or ```ESC``` will close the program. ### Other invocations There are several ways to run this thing:
1) With no arguments, using click-and-drag
2) With the ```-l``` argument and a path to a ```.lua``` script
3) With no arguments, using stdin for commands
#### Click-and-drag method First, run the program with no arguments. Then, click and hold with the left mouse button and move the mouse around the screen. This will cause small circles of particles to appear. It is possible to change the parameters of this emission, but that requires a script file (the second method) #### Script mode This is the most recommended way to run the program. I recommend running the following script: ```$ ./vis -l test/4_random_long.lua``` This gives you a good taste of what the particle engine can do, although it leaves out a good number of details. The full documentation of script mode is below, see *Writing your own scripts* #### Command mode This mode exists for historical reasons and really isn't intended to be used outside of debugging. That said, there are a number of commands supported: ```command "help" [argument] ```: Invoked as either ```help``` or ```help```, this command lists all valid commands and a small description of each. As a special case, ```help emit``` lists extended information on the emit function. ```command "emit" ```: Emits a bundle of particles specified by the arguments given. Type ```help emit``` to see the arguments; there are too many to list here. ```command "kick" ```: Generates a preconfigured emit of ``` ``` particles. ```command "snare" ```: Generates a preconfigured emit of ``` ``` particles. ```command "strum" ```: Generates a preconfigured emit of ``` ``` particles. ```command "rain" ```: Generates a preconfigured emit of ``` ``` particles. ```command "lua" ```: Loads and executes the lua script ``` ```. ```command "audio" ```: Loads and plays the sound file ``` ```, which unfortunately must be a path to a WAV file. No other formats are supported yet. ```command "exit"```: Terminates the application. Pressing either ```^D``` or ```ESC``` also terminates the application. ### Writing your own scripts This is the fun part. There are two distinct APIs available: Vis and VisUtil. ### Uncertainty: The vast majority of values have an uncertainty counterpart, which is used to adjust the particle emission. For example, the uncertainty in horizontal position ```x``` is denoted as ```ux```, and every particle emitted by this table will be positioned at a random spot between ```x - ux``` and ```x + ux```. Position, radius, angle, life, and color all have uncertainty values which all follow this rule. #### ```module Vis = require("Vis")``` This is the core library. Its fields are: ```userdata Vis.flist```: The ```frame list``` structure. In order to schedule emits, the ```vis``` executable needs to know where the scheduling is stored. This is that location. ```userdata Vis.script```: This field is present in order to perform advanced operations, such as scheduling callbacks (strings of Lua script to be executed after a certain amount of time has passed). This field is ```nil``` when the advanced features are not available, which is only when loading scripts via the ```load ``` command in interactive mode. ```function Vis.debug(...)```: For debugging purposes, this function prints out its arguments to the debugging stream if and only if the program was compiled with debugging enabled (via ```make debug```). ```function Vis.command(Vis.flist, when, command)```: Executes the interactive-mode command ```command``` after ```when``` milliseconds has passed. ```function Vis.emit(...)```: Do not use; see ```VisUtil.make_emit_table```, ```VisUtil.emit_table```, the ```Emit``` class, as well as the documentation on emit tables. ```function Vis.audio(path)```: Load the audio file given by ```path```. ```function Vis.play()```: Plays the current audio file. ```function Vis.pause()```: Pauses the current audio file. ```function Vis.seek(hundreths-of-a-second)```: Seeks the current audio file to the offset given by ```hundredths-of-a-second```. ```function Vis.seekms(Vis.flist, when, milliseconds)```: Sets the current position of the schedule to ```milliseconds``` when ```when``` milliseconds have passed. ```function Vis.seekframe(Vis.flist, when, frame_number)```: Sets the current position of the schedule to ```frame_number``` after ```when``` milliseconds have passed. See ```Vis.frames2msec``` and ```Vis.msec2frames``` to convert between frames and milliseconds. ```function Vis.bgcolor(Vis.script, r, g, b)```: Sets the background color to the values given. Each value is between 0 and 1. ```function Vis.mutate(Vis.flist, when, func, coefficient)```: Schedules the mutation given by ```func``` (see constants ```Vis.MUTATE_*```) with coefficient ```coefficient``` after ```when``` milliseconds have passed. Mutates affect all living particles on the screen. ```function Vis.callback(Vis.flist, when, Vis.script, lua_script)```: Executes ```lua_script``` in the same context as the calling file after ```when``` milliseconds have passed. ```function Vis.fps()```: Returns the current derived frames per second, which should be fairly close to ```Vis.FPS_LIMIT```. ```function Vis.settrace(...)```: Not yet documented. ```function Vis.frames2msec(frame_number)```: Converts argument ```frame_number``` to milliseconds. ```function Vis.msec2frames(milliseconds)```: Converts argument ```milliseconds``` to a frame number. ```constant Vis.FPS_LIMIT```: The intended frames-per-second at which this program runs. At the time of writing, this is set to 30. ```constant Vis.WIDTH```: The width of the program window. Currently set to 800 but can be changed. ```constant Vis.HEIGHT```: The height of the program window. Currently set to 600 but can be changed. ```constant Vis.DEFAULT_BLEND```: Equal to Vis.BLEND_NONE ```constant Vis.BLEND_NONE```: Particles do not fade out; they simply vanish suddenly once their lifetime runs out. ```constant Vis.BLEND_LINEAR```: Particles fade out at a constant rate during their lifetime. ```constant Vis.BLEND_QUADRATIC```: Particles start fading slowly and fade faster as they age. ```constant Vis.BLEND_NEGGAMMA```: Particles fade almost suddenly at the end of their lifetime. ```constant Vis.NBLENDS```: Equal to the number of possible blend effects. ```constant Vis.DEFAULT_FORCE```: Particles do not undergo any additional force as they age. ```constant Vis.FORCE_FRICTION```: Particles gradually slow down as they age. ```constant Vis.FORCE_GRAVITY```: Particles gradually accelerate towards the bottom of the screen as they age. ```constant Vis.NFORCES```: Equal to the number of possible force effects. ```constant Vis.DEFAULT_LIMIT```: Particles are free to move off of the screen. ```constant Vis.LIMIT_BOX```: Particles stop at the edges of the screen. ```constant Vis.LIMIT_SPRINGBOX```: Particles bounce off the edges of the screen. ```constant Vis.NLIMITS```: Equal to the number of possible limit choices. ```constant Vis.MUTATE_PUSH```: Accelerates the particles by the coefficient given. ```constant Vis.MUTATE_SLOW```: Decelerates the particles by the coefficient given. ```constant Vis.MUTATE_SHRINK```: Shrinks the particles by the coefficient given. ```constant Vis.MUTATE_GROW```: Grows the particles by the coefficient given. ```constant Vis.MUTATE_PUSH_DX```: Accelerates the particles in the horizontal direction by the coefficient given. ```constant Vis.MUTATE_PUSH_DY```: Accelerates the particles in the vertical direction by the coefficient given. ```constant Vis.MUTATE_AGE```: Sets the age of the particles to the product of their total lifetime and the coefficient given. ```constant Vis.MUTATE_OPACITY```: Sets the particle base opacity to the coefficient given. The blending functions are multiplied by this value. ```constant Vis.MUTATE_TAG_SET```: Tags all particles by setting their tag value to the integer given. ```constant Vis.MUTATE_TAG_INC```: Increases all particles' tags by one. ```constant Vis.MUTATE_TAG_DEC```: Decreases all particles' tags by one. ```constant Vis.MUTATE_TAG_ADD```: Adds the value given to all particles' tags. ```constant Vis.MUTATE_TAG_SUB```: Subtracts the value given from all particles' tags. ```constant Vis.MUTATE_TAG_MUL```: Multiplies all particles' tags by the value given. ```constant Vis.MUTATE_TAG_DIV```: Divides all particles' tags by the value given. ```constant Vis.MUTATE_PUSH_IF```: Apply ```Vis.MUTATE_PUSH``` if the particles' tag and the tag value specified satisfies the condition specified (see ```Vis.MUTATE_IF_*``` below) ```constant Vis.MUTATE_PUSH_DX_IF```: Apply ```Vis.MUTATE_PUSH_DX``` if the condition specified evaluates to true against the particle and the tag given. ```constant Vis.MUTATE_PUSH_DY_IF```: As above, with ```Vis.MUTATE_PUSH_DY``` ```constant Vis.MUTATE_SLOW_IF```: As above, with ```Vis.MUTATE_SLOW``` ```constant Vis.MUTATE_SHRINK_IF```: As above, with ```Vis.MUTATE_SHRINK``` ```constant Vis.MUTATE_GROW_IF```: As above, with ```Vis.MUTATE_GROW``` ```constant Vis.MUTATE_AGE_IF```: As above, with ```Vis.MUTATE_AGE``` ```constant Vis.MUTATE_OPACITY_IF```: As above, with ```Vis.MUTATE_OPACITY``` ```constant Vis.NMUTATES```: Equal to the number of possible mutate choices listed above. ```constant Vis.MUTATE_IF_TRUE```: Satisfied for all particles regardless of their tag value. ```constant Vis.MUTATE_IF_EQ```: Satisfied when the particle's tag equals the tag given. ```constant Vis.MUTATE_IF_NE```: Satisfied when the particle's tag does not equal the tag given. ```constant Vis.MUTATE_IF_LT```: Satisfied when the particle's tag is less than the tag given. ```constant Vis.MUTATE_IF_LE```: Satisfied when the particle's tag is less than or equal to the tag given. ```constant Vis.MUTATE_IF_GT```: Satisfied when the particle's tag is greater that the tag given. ```constant Vis.MUTATE_IF_GE```: Satisfied when the particle's tag is greater than or equal to the tag given. ```constant Vis.MUTATE_IF_EVEN```: Satisfied when the particle's tag is even. ```constant Vis.MUTATE_IF_ODD```: Satisfied when the particle's tag is odd. ```constant Vis.FORCE_FRICTION_COEFF```: The strength of friction, from 0 to 1. ```constant Vis.FORCE_GRAVITY_FACTOR```: The strength of gravity, from 0 to 1. ```constant Vis.NFRAMES```: The maximum number of frames that can be scheduled. This is set to ```1048576``` which equates to a little over nine hours and forty-two minutes. ```constant Vis.AUDIO_FREQ```: The frequency of audio playback, set to 44.1KHz. ```constant Vis.AUDIO_SAMPLES```: The number of samples per frame, set to 512. ```constant Vis.AUDIO_CHANNELS```: The number of channels, set to 2. ```constant Vis.CONST_PUSH_STOP```: Special number that, when passed with ```Vis.MUTATE_PUSH``` or any other mutate function starting with ```MUTATE_PUSH```, stops all particles. ```constant Vis.CONST_AGE_BORN```: Special number that, when passed with ```Vis.MUTATE_AGE``` or ```Vis.MUTATE_AGE_IF```, resets all particles' ages. ```constant Vis.CONST_AGE_DEAD```: Special number that, instead of resetting particle ages, causes particles to die by setting their age to the value representing end-of-life. ```constant Vis.DEBUG```: The value of the ```DEBUG``` macro, set when the program is compiled. It cannot be changed after compilation. It can be one of the following values: ```constant Vis.DEBUG_NONE```: No debugging. ```constant Vis.DEBUG_VERBOSE```: A little debugging. ```constant Vis.DEBUG_DEBUG```: All debugging, other than really noisy stuff. ```constant Vis.DEBUG_INFO```: Include more noisy debug messages. ```constant Vis.DEBUG_TRACE```: Include all debug messages, regardless of noise. This includes allocation and deallocation functions, for tracking down memory leaks. #### ```module VisUtil = require("visutil")``` and the emit table This module is pure Lua and resides in ```lua/visutil.lua```. Have a look there to see how everything is implemented. The emit table granted by the ```VisUtil``` functions has a large number of members, all of which are public and freely modifiable. ```function VisUtil.make_emit_table()```: Returns a newly-constructed emit table with the position set to the center of the screen, radius set to 1, color set to white, and blend set to linear. ```function VisUtil.center_emit_table(table, x, y, ux, uy)```: Centers the emit table ```table``` at ```x``` and ```y```, with a variance of ```ux``` and ```uy```. With only one argument, the table is centered at the center of the screen and the variances are set to zero. Negative values wrap around the screen from the opposite side; ```-VIS_WIDTH/4 == 3*VIS_WIDTH/4``` ```function VisUtil.color_emit_table(table, r, g, b, ur, ug, ub)```: Sets the color to the values given, with variances given by ```ur```, ```ug```, and ```ub```. Colors can be either from 0 to 1, or from 0 to 255. Omitted values are set to zero. ```function VisUtil.emit_table(table)```: Invokes ```Vis.emit```, passing the table given by ```table```. ```function VisUtil.seek_to(milliseconds)```: Invokes both ```Vis.seek()``` and ```Vis.seekms``` with the value given. ```function VisUtil.set_trace_table(table)```: Invokes ```Vis.settrace```, passing the table given. This is how you modify the click-and-drag emission parameters. #### The emit table The keys of a valid emit table and the fields wrapped by an ```Emit``` instance are listed below. ```integer count```: The number of particles to emit. ```integer when```: The time (in milliseconds) at which to emit this frame. ```integers x, ux```: The horizontal position of emission, ```0 <= x < Vis.WIDTH```. Negative values are allowed and wrap around from the right side of the screen. ```integers y, uy```: The vertical position of emission, ```0 <= y < Vis.HEIGHT```. Negative values are allowed and wrap around from the bottom of the screen. ```floats radius, uradius```: The radius, in pixels, of the particles to emit. As a special case, a particle with a radius of 1 will be a single pixel unless argument ```-e``` is passed to ```vis``` on the command-line. ```floats ds, uds```: The initial speed of the particles to emit, measured in pixels per frame. ```floats theta, utheta```: The angle at which to emit the particles. ```integers life, ulife```: The lifetime, in milliseconds, of the particles to emit. ```floats r, g, b, ur, ug, ub```: The color of the particles to emit. It is recommended to use ```VisUtil.color_emit_table``` or ```Emit:color``` over using these values directly. ```enumeration force```: One of the ```Vis.FORCE_``` values. Passing a value other than these will cause errors and, in certain circumstances, may cause the program to terminate. ```enumeration limit```: One of the ```Vis.LIMIT_``` values. Passing a value other than these will cause errors and, in certain circumstances, may cause the program to terminate. ```enumeration blender```: One of the ```Vis.BLEND_``` values. As above, passing values other than these will cause errors and, in certain circumstances, may cause the program to terminate. #### ```module Emit = require('emit')``` and the ```Emit``` class This module is also pure Lua and resides in ```lua/emit.lua```. Have a look there to see how it works. Its purpose is to lessen the strain of working with emit tables directly. ```e = Emit:new()```, ```e = Emit:new(other_emit_instance)```: Creates an instance of the emit class, optionally copying from an existing one. ```e2 = e:clone()```, ```e2 = e:copy()```: Clones the existing emit class ```e```. These are identical to ```e2 = Emit:new(e)``` and are provided for convenience. ```e:emit()```: Schedules the emit at the offset configured via ```e:when()```. This is intended to replace ```VisUtil.emit_table()```. ```e:emit_at(when)```: Convenience function which calls ```e:when(when)``` and then ```e:emit()```. ```e:emit_now()```: Bypasses the ```flist``` entirely and performs the emit immediately. This is useful inside a callback function or on one of the existing callbacks, ```Vis.on_keydown```, ```Vis.on_mousemove```, etc. ```e:set_trace()```: Intended to replace ```VisUtil.set_trace_table()```; please use this over the ```VisUtil``` function. ```e:str()```: Converts the emit table to a string, clearly describing its current configuration. ```e:get(field)```: Returns the current value of the field specified, where ```field``` is one of the fields in the listing above. ```e:set(field, value)```: Sets the value of the field given to the value specified. Use this only when there are no other functions that perform the operation you're trying to accomplish. ```e:count(n)```: Configure the number of particles to emit. ```e:when(t)```: Configure the time to emit, in milliseconds. ```e:center(x, y)```: Places the center of the emit at the position given. Omitting ```x``` and ```y``` will place the emit at the center of the screen. ```e:radius(radius, uradius)```: Configure the emit's radius. The second parameter can be omitted and defaults to zero. ```e:ds(ds, uds)```: Configure the emit's initial velocity. The second parameter defaults to zero. ```e:theta(theta, utheta)```: Configure the emit's angle. The second parameter defaults to zero. ```e:life(life, ulife)```: Configure the emit's lifetime to the value given, in milliseconds. The second parameter defaults to zero. ```e:color(r, g, b, ur, ug, ub)```: Configure the emit's color to the values given, which should be between 0 and 1. Any omitted value defaults to zero. ```e:color2(rgb)```: Configure the emit's color to the entries of array ```rgb```, which should have six total entries. ```e:color3(rgb, urgb)```: Configure the emit's color to the entries of both arrays. The second array, if omitted, defaults to a sequence of three zeros. ```e:force(force)```: Configure the emit's force method to the value given. Value must be one of the ```Vis.FORCE_``` constants. ```e:limit(limit)```: Configure the emit's limit method to the value given. Value must be one of the ```Vis.LIMIT_``` constants. ```e:blender(blend)``` Configure the emit's alpha-blending method to the method given, which must be one of the ```Vis.BLEND_``` constants. ### Mutation Mutates are a very powerful type of frame and provide a way to modify either all particles on screen or only a subset, using tag modification and conditional mutates. Mutates only affect particles already on screen; they do not affect particles yet to be emitted. There are three kinds of mutates, each with their own parameter list. #### Standard mutate ```Vis.mutate(Vis.flist, , , )``` This applies ``` ```, one of the ```Vis.MUTATE_``` values, to all living particles currently on screen. Nearly all the operations require a double-precision number, ``` ```. For example, the operation ```Vis.MUTATE_GROW``` multiples all particles' radii by the coefficient given. Note that this excludes the ```Vis.MUTATE_TAG_``` operations and the ```Vis.MUTATE_IF_``` values as those are covered below. #### Tag modification ```Vis.mutate(Vis.flist, , , )``` where ``` ``` is one of the ```Vis.MUTATE_TAG_``` values. This modifies all active particles currently on screen, adjusting their tag by the operation and value given. For example, ```Vis.MUTATE_TAG_SET``` with a value of ```5``` will set all active particles' tags to the number 5. #### Conditional mutate ```Vis.mutate(Vis.flist, , _IF, , , )``` This applies ``` ``` to all particles on screen if (and only if) both the particle's tag and the ``` ``` value given satisfy the ``` ``` given. For example, ```Vis.mutate(Vis.flist, 50, Vis.MUTATE_PUSH_IF, 2, Vis.MUTATE_IF_EQ, 1)``` will double all particles' velocities if and only if the particle's tag is equal to 1. ## Credit where credit is due Thank you SNESmusic.org for the Bowser theme, retrieved at http://snesmusic.org/v2/profile.php?profile=set&selected=2757 on the 8th of October 2011. Thank you Kainui from ##math at irc.freenode.net for helping me with the blending functions. It would seem my algebra skills are lacking since I left university.
本源码包内暂不包含可直接显示的源代码文件,请下载源码包。