1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-05-02 00:59:37 +03:00
inform7/inform7/multimedia-module/Chapter 2/Sound Effects.w

244 lines
7.7 KiB
OpenEdge ABL

[Sounds::] Sound Effects.
To register the names associated with sound resource numbers, which are defined
to allow the final story file to play sound effects.
@ The following is called to activate the feature:
=
void Sounds::start(void) {
PluginCalls::plug(MAKE_SPECIAL_MEANINGS_PLUG, Sounds::make_special_meanings);
PluginCalls::plug(NEW_BASE_KIND_NOTIFY_PLUG, Sounds::new_base_kind_notify);
PluginCalls::plug(NEW_INSTANCE_NOTIFY_PLUG, Sounds::new_named_instance_notify);
PluginCalls::plug(PRODUCTION_LINE_PLUG, Sounds::production_line);
}
int Sounds::production_line(int stage, int debugging, stopwatch_timer *sequence_timer) {
if (stage == INTER1_CSEQ) {
BENCH(RTMultimedia::compile_sounds);
}
return FALSE;
}
@h One special meaning.
We add one special meaning for assertions, to catch sentences with the shape
"Sound... is the file...".
=
int Sounds::make_special_meanings(void) {
SpecialMeanings::declare(Sounds::new_sound_SMF, I"new-sound", 2);
return FALSE;
}
int Sounds::new_sound_SMF(int task, parse_node *V, wording *NPs) {
wording SW = (NPs)?(NPs[0]):EMPTY_WORDING;
wording OW = (NPs)?(NPs[1]):EMPTY_WORDING;
switch (task) {
case ACCEPT_SMFT:
if ((<nounphrase-sound>(SW)) && (<new-sound-sentence-object>(OW))) {
parse_node *O = <<rp>>;
<np-unparsed>(SW);
V->next = <<rp>>;
V->next->next = O;
return TRUE;
}
break;
case PASS_1_SMFT:
Sounds::register_sound(Node::get_text(V->next),
Node::get_text(V->next->next));
break;
}
return FALSE;
}
@ And this is the Preform grammar needed:
=
<new-sound-sentence-object> ::=
<definite-article> <new-sound-sentence-object-unarticled> | ==> { pass 2 }
<new-sound-sentence-object-unarticled> ==> { pass 1 }
<new-sound-sentence-object-unarticled> ::=
file <np-unparsed> ==> { TRUE, RP[1] }
<nounphrase-sound> ::=
sound ... ==> { 0, Diagrams::new_UNPARSED_NOUN(W) }
@ The syntax for sound effects allows for alt-texts, exactly as for figures.
=
<sound-sentence-object> ::=
<sound-source> ( <quoted-text> ) | ==> { R[1], -, <<alttext>> = R[2] }
<sound-source> ==> { pass 1 }
<sound-source> ::=
<quoted-text> | ==> { pass 1 }
... ==> @<Issue PM_SoundNotTextual problem@>;
@<Issue PM_SoundNotTextual problem@> =
StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_SoundNotTextual),
"a sound effect can only be declared as a quoted file name",
"which should be the name of an AIFF or OGG file inside the Sounds "
"subfolder of the project's .materials folder. For instance, 'Sound "
"of Swordplay is the file \"Crossed Swords.aiff\".'");
==> { 0, - };
@ In assertion pass 1, then, the following is called on any sentence which
has been found to create a sound:
=
void Sounds::register_sound(wording W, wording FN) {
<<alttext>> = -1;
if (<sound-sentence-object>(FN)) {
int wn = <<r>>;
if (wn > 0) Word::dequote(wn);
if (<<alttext>> > 0) Word::dequote(<<alttext>>);
@<Make sure W is acceptable as a new sound name@>;
int id = Task::get_next_free_blorb_resource_ID();
TEMPORARY_TEXT(leaf)
WRITE_TO(leaf, "%N", wn);
if (Str::is_whitespace(leaf)) {
StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_SoundWhiteSpace),
"this is not a filename I can use",
"because it is either empty or contains only spaces.");
return;
}
filename *sound_file = ResourceFinder::find_resource(Task::sounds_department(), leaf, FN);
DISCARD_TEXT(leaf)
if (sound_file) {
Sounds::sounds_create(W, id, sound_file, <<alttext>>);
LOGIF(MULTIMEDIA_CREATIONS,
"Created sound effect <%W> = filename '%N' = resource ID %d\n", W, wn, id);
}
}
}
@<Make sure W is acceptable as a new sound name@> =
Assertions::Creator::vet_name_for_noun(W);
if ((<s-value>(W)) && (Rvalues::is_CONSTANT_of_kind(<<rp>>, K_sound_name))) {
StandardProblems::sentence_problem(Task::syntax_tree(),
_p_(PM_SoundDuplicate),
"this is already the name of a sound effect",
"so there must be some duplication somewhere.");
return;
}
@h One significant kind.
= (early code)
kind *K_sound_name = NULL;
@ This is created by an Inter kit early in Inform's run; the function below
detects that this has happened, and sets |K_sound_name| to point to it.
=
int Sounds::new_base_kind_notify(kind *new_base, text_stream *name, wording W) {
if (Str::eq_wide_string(name, L"SOUND_NAME_TY")) {
K_sound_name = new_base; return TRUE;
}
return FALSE;
}
@h Significant new instances.
This structure of additional data is attached to each sound instance:
=
typedef struct sounds_data {
struct wording name; /* text of name */
struct filename *filename_of_sound_file; /* relative to the Resources folder */
int sound_number; /* resource number of this picture inside Blorb */
int alt_description; /* word number of double-quoted description */
struct instance *as_instance;
CLASS_DEFINITION
} sounds_data;
@ We allow instances of "sound name" to be created only through the above
code calling //Sounds::sounds_create//. If any other proposition somehow
manages to make a sound, a problem message is thrown.
=
int allow_sound_creations = FALSE;
instance *Sounds::sounds_create(wording W, int id, filename *sound_file, int alt) {
allow_sound_creations = TRUE;
Assert::true(Propositions::Abstract::to_create_something(K_sound_name, W), CERTAIN_CE);
allow_sound_creations = FALSE;
instance *I = Instances::latest();
sounds_data *sd = FEATURE_DATA_ON_INSTANCE(sounds, I);
sd->filename_of_sound_file = sound_file;
sd->name = W;
sd->sound_number = id;
sd->alt_description = alt;
sd->as_instance = I;
return I;
}
int Sounds::new_named_instance_notify(instance *I) {
if ((K_sound_name) && (Kinds::eq(Instances::to_kind(I), K_sound_name))) {
if (allow_sound_creations == FALSE)
StandardProblems::sentence_problem(Task::syntax_tree(),
_p_(PM_BackdoorSoundCreation),
"this is not the way to create a new sound name",
"which should be done with a special 'Sound ... is the file ...' "
"sentence.");
ATTACH_FEATURE_DATA_TO_SUBJECT(sounds, I->as_subject, CREATE(sounds_data));
return TRUE;
}
return FALSE;
}
@h Blurb and manifest.
The sounds manifest is used by the implementation of Glulx within the Inform
application to connect picture ID numbers with filenames relative to the
|.materials| folder for its project. (It's part of the XML manifest file
created from |Figures.w|.)
=
void Sounds::write_sounds_manifest(OUTPUT_STREAM) {
if (FEATURE_INACTIVE(sounds)) return;
sounds_data *sd;
if (NUMBER_CREATED(sounds_data) == 0) return;
WRITE("<key>Sounds</key>\n");
WRITE("<dict>\n"); INDENT;
LOOP_OVER(sd, sounds_data) {
WRITE("<key>%d</key>\n", sd->sound_number);
TEMPORARY_TEXT(rel)
Filenames::to_text_relative(rel, sd->filename_of_sound_file,
Projects::materials_path(Task::project()));
WRITE("<string>%S</string>\n", rel);
DISCARD_TEXT(rel)
}
OUTDENT; WRITE("</dict>\n");
}
@ The following writes Blurb commands for all of the sounds.
=
void Sounds::write_blurb_commands(OUTPUT_STREAM) {
if (FEATURE_INACTIVE(sounds)) return;
sounds_data *sd;
LOOP_OVER(sd, sounds_data) {
wchar_t *desc = L"";
if (sd->alt_description >= 0)
desc = Lexer::word_text(sd->alt_description);
if (Wide::len(desc) > 0)
WRITE("sound %d \"%f\" \"%N\"\n",
sd->sound_number, sd->filename_of_sound_file, sd->alt_description);
else
WRITE("sound %d \"%f\"\n", sd->sound_number, sd->filename_of_sound_file);
}
}
@ The following is used only with the "separate figures" release option.
=
void Sounds::write_copy_commands(release_instructions *rel) {
if (FEATURE_INACTIVE(sounds)) return;
sounds_data *sd;
LOOP_OVER(sd, sounds_data)
ReleaseInstructions::add_aux_file(rel,
sd->filename_of_sound_file,
Task::released_sounds_path(),
L"--",
SEPARATE_SOUNDS_PAYLOAD);
}